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

Commit

Permalink
Merge pull request #43 from juanjux/feature/xpath_functions
Browse files Browse the repository at this point in the history
Typed filter functions for queries with boolean/number/string results
  • Loading branch information
juanjux committed Feb 22, 2018
2 parents 4cd1518 + 9ba708f commit 2a3f77b
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 31 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# libuast source code
*.c
*.cc
*.h
!bindings.h

Expand All @@ -10,4 +11,4 @@ Makefile.main
.ci
bin
coverage.txt
profile.out
profile.out
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Package configuration
PROJECT = client-go
LIBUAST_VERSION=.1.8.1
LIBUAST_VERSION=.1.8.2
GOPATH ?= $(shell go env GOPATH)

ifneq ($(OS),Windows_NT)
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ defer iter.Dispose()
for node := range iter.Iterate() {
fmt.Println(node)
}
// For XPath expressions returning a boolean/numeric/string value, you must
// use the right typed Filter function:
boolres, err := FilterBool(res.UAST, "boolean(//*[@strtOffset or @endOffset])")
strres, err := FilterString(res.UAST, "name(//*[1])")
numres, err := FilterNumber(res.UAST, "count(//*)")
```

Please read the [Babelfish clients](https://doc.bblf.sh/user/language-clients.html) guide section to learn more about babelfish clients and their query language.
Expand Down
125 changes: 97 additions & 28 deletions tools/bindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package tools

import (
"fmt"
"runtime/debug"
"sort"
"sync"
"unsafe"
Expand Down Expand Up @@ -50,7 +49,7 @@ var findMutex sync.Mutex
var itMutex sync.Mutex
var pool cstringPool

// Traversal strategy for UAST trees
// TreeOrder represents the traversal strategy for UAST trees
type TreeOrder int
const (
// PreOrder traversal
Expand Down Expand Up @@ -79,6 +78,29 @@ func ptrToNode(ptr C.uintptr_t) *uast.Node {
return (*uast.Node)(unsafe.Pointer(uintptr(ptr)))
}

// initFilter converts the query string and node pointer to C types. It acquires findMutex
// and initializes the string pool. The caller should call deferFilter() after to release
// the resources.
func initFilter(node *uast.Node, xpath string) (*C.char, C.uintptr_t) {
findMutex.Lock()
cquery := pool.getCstring(xpath)
ptr := nodeToPtr(node)

return cquery, ptr
}

func deferFilter() {
findMutex.Unlock()
pool.release()
}

func errorFilter(name string) error {
error := C.Error()
errorf := fmt.Errorf("%s() failed: %s", name, C.GoString(error))
C.free(unsafe.Pointer(error))
return errorf
}

// Filter takes a `*uast.Node` and a xpath query and filters the tree,
// returning the list of nodes that satisfy the given query.
// Filter is thread-safe but not concurrent by an internal global lock.
Expand All @@ -87,25 +109,11 @@ func Filter(node *uast.Node, xpath string) ([]*uast.Node, error) {
return nil, nil
}

// Find is not thread-safe bacause of the underlining C API
findMutex.Lock()
defer findMutex.Unlock()
cquery, ptr := initFilter(node, xpath)
defer deferFilter()

// convert go string to C string
cquery := pool.getCstring(xpath)

// Make sure we release the pool of strings
defer pool.release()

// stop GC
gcpercent := debug.SetGCPercent(-1)
defer debug.SetGCPercent(gcpercent)

ptr := nodeToPtr(node)
if !C.Filter(ptr, cquery) {
error := C.Error()
return nil, fmt.Errorf("UastFilter() failed: %s", C.GoString(error))
C.free(unsafe.Pointer(error))
return nil, errorFilter("UastFilter")
}

nu := int(C.Size())
Expand All @@ -116,6 +124,74 @@ func Filter(node *uast.Node, xpath string) ([]*uast.Node, error) {
return results, nil
}

// FilterBool takes a `*uast.Node` and a xpath query with a boolean
// return type (e.g. when using XPath functions returning a boolean type).
// FilterBool is thread-safe but not concurrent by an internal global lock.
func FilterBool(node *uast.Node, xpath string)(bool, error) {
if len(xpath) == 0 {
return false, nil
}

cquery, ptr := initFilter(node, xpath)
defer deferFilter()

res := C.FilterBool(ptr, cquery)
if (res < 0) {
return false, errorFilter("UastFilterBool")
}

var gores bool
if res == 0 {
gores = false
} else if res == 1{
gores = true
} else {
panic("Implementation error on FilterBool")
}

return gores, nil
}

// FilterBool takes a `*uast.Node` and a xpath query with a float
// return type (e.g. when using XPath functions returning a float type).
// FilterNumber is thread-safe but not concurrent by an internal global lock.
func FilterNumber(node *uast.Node, xpath string)(float64, error) {
if len(xpath) == 0 {
return 0.0, nil
}

cquery, ptr := initFilter(node, xpath)
defer deferFilter()

var ok C.int
res := C.FilterNumber(ptr, cquery, &ok)
if (ok == 0) {
return 0.0, errorFilter("UastFilterNumber")
}

return float64(res), nil
}

// FilterString takes a `*uast.Node` and a xpath query with a string
// return type (e.g. when using XPath functions returning a string type).
// FilterString is thread-safe but not concurrent by an internal global lock.
func FilterString(node *uast.Node, xpath string)(string, error) {
if len(xpath) == 0 {
return "", nil
}

cquery, ptr := initFilter(node, xpath)
defer deferFilter()

var res *C.char
res = C.FilterString(ptr, cquery)
if (res == nil) {
return "", errorFilter("UastFilterString")
}

return C.GoString(res), nil
}

//export goGetInternalType
func goGetInternalType(ptr C.uintptr_t) *C.char {
return pool.getCstring(ptrToNode(ptr).InternalType)
Expand Down Expand Up @@ -266,16 +342,13 @@ func NewIterator(node *uast.Node, order TreeOrder) (*Iterator, error) {
itMutex.Lock()
defer itMutex.Unlock()

// stop GC
gcpercent := debug.SetGCPercent(-1)
defer debug.SetGCPercent(gcpercent)

ptr := nodeToPtr(node)
it := C.IteratorNew(ptr, C.int(order))
if it == 0 {
error := C.Error()
return nil, fmt.Errorf("UastIteratorNew() failed: %s", C.GoString(error))
errorf := fmt.Errorf("UastIteratorNew() failed: %s", C.GoString(error))
C.free(unsafe.Pointer(error))
return nil, errorf
}

return &Iterator {
Expand All @@ -295,10 +368,6 @@ func (i *Iterator) Next() (*uast.Node, error) {
return nil, fmt.Errorf("Next() called on finished iterator")
}

// stop GC
gcpercent := debug.SetGCPercent(-1)
defer debug.SetGCPercent(gcpercent)

pnode := C.IteratorNext(i.iterPtr);
if pnode == 0 {
// End of the iteration
Expand Down
26 changes: 25 additions & 1 deletion tools/bindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,30 @@ static bool Filter(uintptr_t node_ptr, const char *query) {
return nodes != NULL;
}

static int FilterBool(uintptr_t node_ptr, const char *query) {
bool ok;
bool res = UastFilterBool(ctx, (void*)node_ptr, query, &ok);
if (!ok) {
return -1;
}
return (int)res;
}

static double FilterNumber(uintptr_t node_ptr, const char *query, int *ok) {
bool c_ok;
double res = UastFilterNumber(ctx, (void*)node_ptr, query, &c_ok);
if (!c_ok) {
*ok = 0;
} else {
*ok = 1;
}
return res;
}

static const char *FilterString(uintptr_t node_ptr, const char *query) {
return UastFilterString(ctx, (void*)node_ptr, query);
}

static uintptr_t IteratorNew(uintptr_t node_ptr, int order) {
return (uintptr_t)UastIteratorNew(ctx, (void *)node_ptr, order);
}
Expand All @@ -161,7 +185,7 @@ static uintptr_t IteratorNext(uintptr_t iter) {
return (uintptr_t)UastIteratorNext((void*)iter);
}

static uintptr_t IteratorFree(uintptr_t iter) {
static void IteratorFree(uintptr_t iter) {
UastIteratorFree((void*)iter);
}

Expand Down
41 changes: 41 additions & 0 deletions tools/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,47 @@ func TestFilter(t *testing.T) {
assert.Nil(t, err)
}

func TestFilterWrongType(t *testing.T) {
n := &uast.Node{}

_, err := Filter(n, "boolean(//*[@startPosition or @endPosition])")
assert.NotNil(t, err)
}

func TestFilterBool(t *testing.T) {
n := &uast.Node{}

r, err := FilterBool(n, "boolean(0)")
assert.Nil(t, err)
assert.False(t, r)

r, err = FilterBool(n, "boolean(1)")
assert.Nil(t, err)
assert.True(t, r)
}

func TestFilterNumber(t *testing.T) {
n := &uast.Node{}

r, err := FilterNumber(n, "count(//*)")
assert.Nil(t, err)
assert.Equal(t, int(r), 1)

n.Children = []*uast.Node{&uast.Node{}, &uast.Node{}}
r, err = FilterNumber(n, "count(//*)")
assert.Nil(t, err)
assert.Equal(t, int(r), 3)
}

func TestFilterString(t *testing.T) {
n := &uast.Node{}
n.InternalType = "TestType"

r, err := FilterString(n, "name(//*[1])")
assert.Nil(t, err)
assert.Equal(t, r, "TestType")
}

func TestFilter_All(t *testing.T) {
n := &uast.Node{}

Expand Down

0 comments on commit 2a3f77b

Please sign in to comment.