Skip to content
This repository was archived by the owner on Mar 8, 2020. It is now read-only.

Commit c1d6d95

Browse files
authored
Merge pull request #77 from juanjux/feature/typed_xpathresults
Added libuast typed filter functions. Other fixes & factorizations.
2 parents 3b71f52 + 61f9e7d commit c1d6d95

File tree

5 files changed

+123
-23
lines changed

5 files changed

+123
-23
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ print(uast)
5252
# "filter' allows you to use XPath queries to filter on result nodes:
5353
print(bblfsh.filter(uast, "//Import[@roleImport and @roleDeclaration]//alias")
5454

55+
# filter\_[bool|string|number] must be used when using XPath functions returning
56+
# these types:
57+
print(bblfsh.filter_bool(uast, "boolean(//*[@strtOffset or @endOffset])"))
58+
print(bblfsh.filter_string(uast, "name(//*[1])"))
59+
print(bblfsh.filter_number(uast, "count(//*)"))
60+
5561
# You can also iterate on several tree iteration orders:
5662
it = bblfsh.iterator(uast, bblfsh.TreeOrder.PRE_ORDER)
5763
for node in it:

bblfsh/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from bblfsh.client import BblfshClient
2-
from bblfsh.pyuast import filter, iterator
2+
from bblfsh.pyuast import filter, filter_bool, filter_number, filter_string, iterator
33
from bblfsh.aliases import *
44

55
class TreeOrder:

bblfsh/pyuast.c

Lines changed: 86 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ static size_t Size(const void *node, const char *prop) {
3939
PyObject *o = Attribute(node, prop);
4040
if (o != NULL) {
4141
retval = PySequence_Size(o);
42-
Py_DECREF(o);
4342
}
4443

4544
return retval;
@@ -287,25 +286,42 @@ static void PyUastIter_dealloc(PyObject *self)
287286
UastIteratorFree(((PyUastIter *)self)->iter);
288287
}
289288

289+
static bool initFilter(PyObject *args, PyObject **obj, const char **query)
290+
{
291+
if (!PyArg_ParseTuple(args, "Os", obj, query)) {
292+
return false;
293+
}
294+
295+
itemAtAllocsList = PyList_New(0);
296+
stringAllocsList = PyList_New(0);
297+
return true;
298+
}
299+
300+
static void cleanupFilter(void)
301+
{
302+
Py_DECREF(itemAtAllocsList);
303+
Py_DECREF(stringAllocsList);
304+
}
305+
306+
static void filterError(void)
307+
{
308+
char *error = LastError();
309+
PyErr_SetString(PyExc_RuntimeError, error);
310+
free(error);
311+
cleanupFilter();
312+
}
313+
290314
static PyObject *PyFilter(PyObject *self, PyObject *args)
291315
{
292316
PyObject *obj = NULL;
293317
const char *query = NULL;
294318

295-
if (!PyArg_ParseTuple(args, "Os", &obj, &query)) {
319+
if (!initFilter(args, &obj, &query))
296320
return NULL;
297-
}
298-
299-
itemAtAllocsList = PyList_New(0);
300-
stringAllocsList = PyList_New(0);
301321

302322
Nodes *nodes = UastFilter(ctx, obj, query);
303323
if (!nodes) {
304-
char *error = LastError();
305-
PyErr_SetString(PyExc_RuntimeError, error);
306-
free(error);
307-
Py_DECREF(stringAllocsList);
308-
Py_DECREF(itemAtAllocsList);
324+
filterError();
309325
return NULL;
310326
}
311327
size_t len = NodesSize(nodes);
@@ -320,14 +336,71 @@ static PyObject *PyFilter(PyObject *self, PyObject *args)
320336
PyObject *iter = PySeqIter_New(list);
321337
Py_DECREF(list);
322338

323-
Py_DECREF(itemAtAllocsList);
324-
Py_DECREF(stringAllocsList);
339+
cleanupFilter();
325340
return iter;
326341
}
327342

343+
static PyObject *PyFilterBool(PyObject *self, PyObject *args)
344+
{
345+
PyObject *obj = NULL;
346+
const char *query = NULL;
347+
348+
if (!initFilter(args, &obj, &query))
349+
return NULL;
350+
351+
bool ok;
352+
bool res = UastFilterBool(ctx, obj, query, &ok);
353+
if (!ok) {
354+
filterError();
355+
return NULL;
356+
}
357+
358+
cleanupFilter();
359+
return res ? Py_True : Py_False;
360+
}
361+
362+
static PyObject *PyFilterNumber(PyObject *self, PyObject *args)
363+
{
364+
PyObject *obj = NULL;
365+
const char *query = NULL;
366+
367+
if (!initFilter(args, &obj, &query))
368+
return NULL;
369+
370+
bool ok;
371+
double res = UastFilterNumber(ctx, obj, query, &ok);
372+
if (!ok) {
373+
filterError();
374+
return NULL;
375+
}
376+
377+
cleanupFilter();
378+
return PyFloat_FromDouble(res);
379+
}
380+
381+
static PyObject *PyFilterString(PyObject *self, PyObject *args)
382+
{
383+
PyObject *obj = NULL;
384+
const char *query = NULL;
385+
386+
if (!initFilter(args, &obj, &query))
387+
return NULL;
388+
389+
const char *res = UastFilterString(ctx, obj, query);
390+
if (res == NULL) {
391+
filterError();
392+
return NULL;
393+
}
394+
395+
cleanupFilter();
396+
return PyUnicode_FromString(res);
397+
}
328398
static PyMethodDef extension_methods[] = {
329399
{"filter", PyFilter, METH_VARARGS, "Filter nodes in the UAST using the given query"},
330400
{"iterator", PyUastIter_new, METH_VARARGS, "Get an iterator over a node"},
401+
{"filter_bool", PyFilterBool, METH_VARARGS, "For queries returning boolean values"},
402+
{"filter_number", PyFilterNumber, METH_VARARGS, "For queries returning boolean values"},
403+
{"filter_string", PyFilterString, METH_VARARGS, "For queries returning boolean values"},
331404
{NULL, NULL, 0, NULL}
332405
};
333406

bblfsh/test.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import docker
55

66
from bblfsh import (BblfshClient, filter, iterator, role_id,
7-
role_name, Node, ParseResponse, TreeOrder)
7+
role_name, Node, ParseResponse, TreeOrder, filter_bool,
8+
filter_number, filter_string)
89
from bblfsh.launcher import ensure_bblfsh_is_running
910
from bblfsh.client import NonUTF8ContentException
1011

@@ -28,10 +29,10 @@ def setUp(self):
2829

2930
def testVersion(self):
3031
version = self.client.version()
31-
assert(hasattr(version, "version"))
32-
assert(version.version)
33-
assert(hasattr(version, "build"))
34-
assert(version.build)
32+
self.assertTrue(hasattr(version, "version"))
33+
self.assertTrue(version.version)
34+
self.assertTrue(hasattr(version, "build"))
35+
self.assertTrue(version.build)
3536

3637
def testNativeParse(self):
3738
reply = self.client.native_parse(__file__)
@@ -119,13 +120,33 @@ def testFilterEndCol(self):
119120
self.assertTrue(any(filter(node, "//*[@endCol=50]")))
120121
self.assertFalse(any(filter(node, "//*[@endCol=5]")))
121122

123+
def testFilterBool(self):
124+
node = Node()
125+
self.assertTrue(filter_bool(node, "boolean(//*[@startOffset or @endOffset])"))
126+
self.assertFalse(filter_bool(node, "boolean(//*[@blah])"))
127+
128+
def testFilterNumber(self):
129+
node = Node()
130+
node.children.extend([Node(), Node(), Node()])
131+
self.assertEqual(int(filter_number(node, "count(//*)")), 4)
132+
133+
def testFilterString(self):
134+
node = Node()
135+
node.internal_type = "test"
136+
self.assertEqual(filter_string(node, "name(//*[1])"), "test")
137+
122138
def testFilterBadQuery(self):
123139
node = Node()
124140
self.assertRaises(RuntimeError, filter, node, "//*roleModule")
125141

142+
def testFilterBadType(self):
143+
node = Node()
144+
node.end_position.col = 50
145+
self.assertRaises(RuntimeError, filter, node, "boolean(//*[@startPosition or @endPosition])")
146+
126147
def testRoleIdName(self):
127-
assert(role_id(role_name(1)) == 1)
128-
assert(role_name(role_id("IDENTIFIER")) == "IDENTIFIER")
148+
self.assertEqual(role_id(role_name(1)), 1)
149+
self.assertEqual(role_name(role_id("IDENTIFIER")), "IDENTIFIER")
129150

130151
def _itTestTree(self):
131152
root = Node()
@@ -186,7 +207,7 @@ def _validate_resp(self, resp):
186207
# self.assertIsInstance(resp.uast, Node)
187208

188209
# Sometimes its fully qualified, sometimes is just "Node"... ditto
189-
assert(resp.uast.__class__.__name__.endswith('Node'))
210+
self.assertTrue(resp.uast.__class__.__name__.endswith('Node'))
190211

191212
def testManyFilters(self):
192213
root = self.client.parse(__file__).uast

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def main():
134134
},
135135
name="bblfsh",
136136
description="Fetches Universal Abstract Syntax Trees from Babelfish.",
137-
version="2.8.2",
137+
version="2.9.0",
138138
license="Apache 2.0",
139139
author="source{d}",
140140
author_email="language-analysis@sourced.tech",

0 commit comments

Comments
 (0)