diff --git a/contrib/py-bindings/PyGF.hsc b/contrib/py-bindings/PyGF.hsc index d9f249eab..ddd3920b6 100644 --- a/contrib/py-bindings/PyGF.hsc +++ b/contrib/py-bindings/PyGF.hsc @@ -1,7 +1,9 @@ {-# LANGUAGE ForeignFunctionInterface #-} --- GF Python bindings -- Jordi Saludes, upc.edu 2010 +-- +-- GF Python bindings -- Jordi Saludes, upc.edu 2010 -- + module PyGF where import PGF @@ -9,6 +11,8 @@ import Foreign import CString import Foreign.C.Types import Control.Monad +import Data.Map (keys, (!)) +import Data.Char (isSpace) #include "pygf.h" @@ -73,6 +77,7 @@ instance Storable Tree where deRefStablePtr sp -} + foreign export ccall gf_freePGF :: Ptr PGF -> IO () foreign export ccall gf_freeType :: Ptr Type -> IO () foreign export ccall gf_freeLanguage :: Ptr Language -> IO () @@ -141,6 +146,18 @@ listToPy mk ls = do poke pl l pyl << pl + +listToPyStrings :: [String] -> IO (Ptr ()) +listToPyStrings ss = do + pyls <- pyList + mapM_ (mpoke pyls) ss + return pyls + where mpoke pyl s = do + cs <- newCString s + pcs <- pyString cs + pyl << pcs + + -- foreign export ccall "gf_freeArray" free :: Ptr a -> IO () @@ -256,6 +273,29 @@ gf_functiontype ppgf pcid = do _ -> return nullPtr +foreign export ccall gf_completions :: Ptr PGF -> Ptr Language -> Ptr Type -> CString -> IO (Ptr ()) +gf_completions ppgf plang pcat ctoks = do + pgf <- peek ppgf + lang <- peek plang + cat <- peek pcat + toks <- peekCString ctoks + let (rpre,rs) = break isSpace (reverse toks) + pre = reverse rpre + ws = words (reverse rs) + state0 = initState pgf lang cat + completions = + case loop state0 ws of + Nothing -> [] + Just state -> keys $ getCompletions state pre + listToPyStrings completions + where + loop ps [] = Just ps + loop ps (w:ws) = + case nextState ps (simpleParseInput w) of + Left _ -> Nothing + Right ps -> loop ps ws + + foreign import ccall "newLang" pyLang :: IO (Ptr Language) foreign import ccall "newPGF" pyPGF :: IO (Ptr PGF) foreign import ccall "newTree" pyTree :: IO (Ptr Tree) @@ -263,4 +303,5 @@ foreign import ccall "newgfType" pyType :: IO (Ptr Type) foreign import ccall "newCId" pyCId :: IO (Ptr CId) foreign import ccall "newExpr" pyExpr :: IO (Ptr Expr) foreign import ccall "newList" pyList :: IO (Ptr ()) +foreign import ccall "newString" pyString :: CString -> IO (Ptr ()) foreign import ccall "append" (<<) :: Ptr () -> Ptr a -> IO () diff --git a/contrib/py-bindings/gfmodule.c b/contrib/py-bindings/gfmodule.c index bd4693d2e..dc8f583a7 100644 --- a/contrib/py-bindings/gfmodule.c +++ b/contrib/py-bindings/gfmodule.c @@ -1,5 +1,5 @@ // GF Python bindings -// Jordi Saludes, upc.edu 2010 +// Jordi Saludes, upc.edu 2010, 2011 // #include @@ -33,6 +33,7 @@ NEWGF(Tree,GF_Tree,TreeType,"gf.tree","gf tree") DEALLOCFN(CId_dealloc, CId, gf_freeCId, "freeCId") + /* PGF methods, constructor and destructor */ DEALLOCFN(PGF_dealloc, PGF, gf_freePGF, "freePGF") @@ -123,17 +124,16 @@ parse(PGF *self, PyObject *args, PyObject *kws) Lang *lang; gfType *cat = NULL; char *lexed; - static char *kwlist[] = {"lexed", "lang", "cat", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kws, "sO|O", kwlist, - &lexed, &lang, &cat)) - return NULL; + static char *kwlist[] = {"lang", "lexed", "cat", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kws, "Os|O", kwlist, &lang, &lexed, &cat)) + return NULL; if (!checkType(self, &PGFType)) return NULL; if (!checkType(lang, &LangType)) return NULL; if (cat) { if (!checkType(cat, &gfTypeType)) return NULL; } else { - cat = gf_startCat(self); - } + cat = gf_startCat(self); + } return gf_parse(self, lang, cat, lexed); } @@ -154,9 +154,27 @@ readPGF(PyObject *self, PyObject *args) } +static PyObject* +completions(PGF *self, PyObject *args, PyObject *kws) +{ char *tokens; + Lang *lang; + gfType *cat = NULL; + static char *kwlist[] = {"lang", "tokens", "category", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kws, "Os|O", kwlist, &lang, &tokens, &cat)) + return NULL; + if (!checkType(self, &PGFType)) return NULL; + if (!checkType(lang, &LangType)) return NULL; + if (cat) { + if (!checkType(cat, &gfTypeType)) return NULL; + } else { + cat = gf_startCat(self); + } + return gf_completions(self, lang, cat, tokens); +} + static PyMethodDef pgf_methods[] = { - {"parse", (PyCFunction)parse, METH_VARARGS|METH_KEYWORDS,"Parse a string."}, + {"parse", (PyCFunction)parse, METH_VARARGS|METH_KEYWORDS, "Parse a string."}, {"lin", (PyCFunction)linearize, METH_VARARGS,"Linearize tree."}, {"lang_code", (PyCFunction)languageCode, METH_VARARGS,"Get the language code."}, {"print_name", (PyCFunction)printName, METH_VARARGS,"Get the print name for a id."}, @@ -166,6 +184,7 @@ static PyMethodDef pgf_methods[] = { {"functions", (PyCFunction)gf_functions, METH_NOARGS,"Get all functions."}, {"abstract", (PyCFunction)abstractName, METH_NOARGS,"Get the module abstract name."}, {"languages", (PyCFunction)gf_languages, METH_NOARGS,"Get the module languages."}, + {"complete", (PyCFunction)completions, METH_VARARGS|METH_KEYWORDS, "Get completions for tokens."}, {NULL, NULL, 0, NULL} /* Sentinel */ }; @@ -281,12 +300,17 @@ initgf(void) if (PyType_Ready(&t) < 0) return; READYTYPE(CIdType, CId_repr, CId_dealloc) + PGFType.tp_methods = pgf_methods; READYTYPE(PGFType, pgf_repr, PGF_dealloc) + READYTYPE(LangType, lang_repr, Lang_dealloc) + READYTYPE(gfTypeType, gfType_repr, gfType_dealloc) - ExprType.tp_methods = expr_methods; + + ExprType.tp_methods = expr_methods; READYTYPE(ExprType, expr_repr, expr_dealloc) + TreeType.tp_methods = expr_methods; // Tree == Expr ? READYTYPE(TreeType, tree_repr, Tree_dealloc) @@ -311,4 +335,5 @@ PyModule_AddObject(m, "gf", (PyObject *)&t); /* List utilities to be imported by FFI */ inline PyObject* newList() { return PyList_New(0); } +inline PyObject* newString(const char *s) { return PyString_FromString(s); } inline void append(PyObject* l, PyObject* ob) { PyList_Append(l, ob); } diff --git a/contrib/py-bindings/test.py b/contrib/py-bindings/test.py index ebfa0a0c8..6ec3f77cc 100644 --- a/contrib/py-bindings/test.py +++ b/contrib/py-bindings/test.py @@ -7,7 +7,6 @@ import gf import unittest - samples = [ (['Odd', ['Number', 89]], {'eng': "is 89 odd", @@ -93,7 +92,7 @@ class TestParsing(unittest.TestCase): l = gf.read_language(self.lang) for abs,cnc in self.lexed: rabs = exp2str(abs) - ps = pgf.parse(cnc['eng'], l) + ps = pgf.parse(l, cnc['eng']) self.failUnless(ps) pt = rmprefix(ps[0]) self.assertEqual(pt,rabs) @@ -108,7 +107,7 @@ class TestLinearize(unittest.TestCase): def test_Linearize(self): l = self.lang for abs,cnc in self.samples: - ts = self.pgf.parse(cnc['eng'], l) + ts = self.pgf.parse(l, cnc['eng']) self.assertEqual(cnc['eng'],self.pgf.lin(l,ts[0])) class TestTranslate(unittest.TestCase): @@ -122,7 +121,7 @@ class TestTranslate(unittest.TestCase): for i,l in self.langs: for j,m in self.langs: if i==j: continue - parsed = self.pgf.parse(cnc[i],l) + parsed = self.pgf.parse(l, cnc[i]) assert len(parsed) == 1 lin = self.pgf.lin(m,parsed[0]) self.assertEqual(lin,cnc[j]) @@ -146,7 +145,7 @@ class TestUnapplyExpr(unittest.TestCase): lg = 'eng' lang = self.langs[lg] for abs,cnc in self.samples: - parsed = self.pgf.parse(cnc[lg],lang) + parsed = self.pgf.parse(lang, cnc[lg]) uparsed = self.deep_unapp(parsed[0]) self.assertEqual(abs,uparsed) @@ -154,7 +153,7 @@ class TestUnapplyExpr(unittest.TestCase): lg = 'eng' lang = self.langs[lg] cnc = self.samples[0][1] - parsed = self.pgf.parse(cnc[lg],lang) + parsed = self.pgf.parse(lang, cnc[lg]) exp = parsed[0] for t in 'Question Object Int'.split(): self.assertEqual(`exp.infer(self.pgf)`, t) @@ -163,5 +162,26 @@ class TestUnapplyExpr(unittest.TestCase): exp = uexp[1] +class TestComplete(unittest.TestCase): + def setUp(self): + self.lexed = samples + self.pgf = gf.read_pgf('Query.pgf') + self.langs = dict([(lang2iso(l),l) for l in self.pgf.languages()]) + + def test_complete(self): + for (_,d) in self.lexed: + for l,text in d.items(): + lang = self.langs[l] + for k in range(len(text)): + if text[k].isdigit() or text[k-1].isdigit(): # No completion for integer literals + continue + comps = self.pgf.complete(lang,text[:k]) + self.assertNotEqual(comps, [], + msg="while completing '%s^%s'" % (text[:k],text[k:])) + + self.assertTrue(any(w in text for w in comps), + msg="None of %s is in '%s'" % (comps,text)) + + if __name__ == '__main__': unittest.main()