This repository has been archived by the owner on Mar 8, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from manucorporat/add-lib
Adds client API + simple cli
- Loading branch information
Showing
9 changed files
with
376 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
language: go | ||
sudo: false | ||
go: | ||
- 1.6.x | ||
- 1.7.x | ||
- 1.8.x | ||
- master | ||
|
||
install: | ||
- go get . | ||
|
||
script: | ||
- go test ./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,17 @@ | ||
# client-go | ||
Babelfish Go client | ||
# go-client for babelfish | ||
|
||
Go-client allows to easily comunicate with the Babelfish server to parse the any source code. | ||
It's also integrated with libuast so it is possible to run xpath queries agains the UAST in an idiomatic way. | ||
|
||
|
||
## Installation | ||
|
||
### Dependencies | ||
|
||
- libuast ( https://github.com/bblfsh/libuast ) | ||
|
||
### Go library | ||
|
||
``` | ||
go get github.com/bblfsh/client-go | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
#ifndef _CLIENT_GO_BINDINGS_H_ | ||
#define _CLIENT_GO_BINDINGS_H_ | ||
|
||
#include <libuast/uast.h> | ||
|
||
extern char* goGetInternalType(uintptr_t); | ||
extern int goGetPropertiesSize(uintptr_t); | ||
extern char* goGetToken(uintptr_t); | ||
extern int goGetChildrenSize(uintptr_t); | ||
extern uintptr_t goGetChild(uintptr_t, int); | ||
extern int goGetRolesSize(uintptr_t); | ||
extern uint16_t goGetRole(uintptr_t, int); | ||
|
||
static const char *get_internal_type(const void *node) { | ||
return goGetInternalType((uintptr_t)node); | ||
} | ||
|
||
static const char *get_token(const void *node) { | ||
return goGetToken((uintptr_t)node); | ||
} | ||
|
||
static int get_children_size(const void *node) { | ||
return goGetChildrenSize((uintptr_t)node); | ||
} | ||
|
||
static void *get_child(const void *data, int index) { | ||
return (void*)goGetChild((uintptr_t)data, index); | ||
} | ||
|
||
static int get_roles_size(const void *node) { | ||
return goGetRolesSize((uintptr_t)node); | ||
} | ||
|
||
static uint16_t get_role(const void *node, int index) { | ||
return goGetRole((uintptr_t)node, index); | ||
} | ||
|
||
static node_api *api; | ||
static find_ctx *ctx; | ||
|
||
static void create_go_node_api() { | ||
api = new_node_api((node_iface){ | ||
.internal_type = get_internal_type, | ||
.token = get_token, | ||
.children_size = get_children_size, | ||
.children = get_child, | ||
.roles_size = get_roles_size, | ||
.roles = get_role, | ||
}); | ||
ctx = new_find_ctx(); | ||
} | ||
|
||
static int _api_find(uintptr_t node_ptr, const char *query) { | ||
return node_api_find(api, ctx, (void*)node_ptr, query); | ||
} | ||
|
||
static int _api_get_len() { | ||
return find_ctx_get_len(ctx); | ||
} | ||
|
||
static uintptr_t _api_get_result(unsigned int i) { | ||
return (uintptr_t)find_ctx_get(ctx, i); | ||
} | ||
|
||
#endif // _CLIENT_GO_BINDINGS_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
|
||
"github.com/bblfsh/client-go" | ||
) | ||
|
||
var endpoint = flag.String("e", "localhost:9432", "endpoint of the babelfish server") | ||
var filename = flag.String("f", "", "file to parse") | ||
var query = flag.String("q", "", "xpath expression") | ||
|
||
func main() { | ||
flag.Parse() | ||
if *filename == "" { | ||
fmt.Println("filename was not provided. Use the -f flag") | ||
return | ||
} | ||
|
||
client, err := bblfsh.NewBblfshClient(*endpoint) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
res, err := client.NewParseRequest().ReadFile(*filename).Do() | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
if *query == "" { | ||
fmt.Println(res.UAST) | ||
} else { | ||
results, _ := bblfsh.Find(res.UAST, *query) | ||
for i, node := range results { | ||
fmt.Println("-", i+1, "----------------------") | ||
fmt.Println(node.String()) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package bblfsh | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/bblfsh/sdk/protocol" | ||
"google.golang.org/grpc" | ||
) | ||
|
||
// BblfshClient holds the public client API to interact with the babelfish server. | ||
type BblfshClient struct { | ||
client protocol.ProtocolServiceClient | ||
} | ||
|
||
// NewBblfshClient returns a new babelfish client given a server endpoint | ||
func NewBblfshClient(endpoint string) (*BblfshClient, error) { | ||
conn, err := grpc.Dial(endpoint, grpc.WithTimeout(time.Second*2), grpc.WithInsecure()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &BblfshClient{ | ||
client: protocol.NewProtocolServiceClient(conn), | ||
}, nil | ||
} | ||
|
||
func (c *BblfshClient) NewParseRequest() *ParseRequest { | ||
return &ParseRequest{client: c} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package bblfsh | ||
|
||
// #include <stdlib.h> | ||
import "C" | ||
import "unsafe" | ||
|
||
type cstringPool struct { | ||
pointers []unsafe.Pointer | ||
} | ||
|
||
func (pool *cstringPool) getCstring(str string) *C.char { | ||
ptr := C.CString(str) | ||
pool.pointers = append(pool.pointers, unsafe.Pointer(ptr)) | ||
return ptr | ||
} | ||
|
||
func (*cstringPool) release() { | ||
for _, ptr := range pool.pointers { | ||
C.free(ptr) | ||
} | ||
pool.pointers = pool.pointers[0:0] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package bblfsh | ||
|
||
import ( | ||
"errors" | ||
"runtime/debug" | ||
"sync" | ||
"unsafe" | ||
|
||
"github.com/bblfsh/sdk/uast" | ||
) | ||
|
||
// #cgo CFLAGS: -I/usr/local/include -I/usr/local/include/libxml2 -I/usr/include -I/usr/include/libxml2 | ||
// #cgo LDFLAGS: -luast -lxml2 | ||
// #include "bindings.h" | ||
import "C" | ||
|
||
var findMutex sync.Mutex | ||
var pool cstringPool | ||
|
||
func init() { | ||
C.create_go_node_api() | ||
} | ||
|
||
func nodeToPtr(node *uast.Node) C.uintptr_t { | ||
return C.uintptr_t(uintptr(unsafe.Pointer(node))) | ||
} | ||
|
||
func ptrToNode(ptr C.uintptr_t) *uast.Node { | ||
return (*uast.Node)(unsafe.Pointer(uintptr(ptr))) | ||
} | ||
|
||
// Find takes a `*uast.Node` and a xpath query and filters the tree, | ||
// returning the list of nodes that satisfy the given query. | ||
// Find is thread-safe but not current by an internal global lock. | ||
func Find(node *uast.Node, xpath string) ([]*uast.Node, error) { | ||
// Find is not thread-safe bacuase of the underlining C API | ||
findMutex.Lock() | ||
defer findMutex.Unlock() | ||
|
||
// convert xpath string to a NULL-terminated 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._api_find(ptr, cquery) != 0 { | ||
return nil, errors.New("error: node_api_find() failed") | ||
} | ||
|
||
nu := int(C._api_get_len()) | ||
results := make([]*uast.Node, nu) | ||
for i := 0; i < nu; i++ { | ||
results[i] = ptrToNode(C._api_get_result(C.uint(i))) | ||
} | ||
return results, nil | ||
} | ||
|
||
//export goGetInternalType | ||
func goGetInternalType(ptr C.uintptr_t) *C.char { | ||
return pool.getCstring(ptrToNode(ptr).InternalType) | ||
} | ||
|
||
//export goGetToken | ||
func goGetToken(ptr C.uintptr_t) *C.char { | ||
return pool.getCstring(ptrToNode(ptr).Token) | ||
} | ||
|
||
//export goGetChildrenSize | ||
func goGetChildrenSize(ptr C.uintptr_t) C.int { | ||
return C.int(len(ptrToNode(ptr).Children)) | ||
} | ||
|
||
//export goGetChild | ||
func goGetChild(ptr C.uintptr_t, index C.int) C.uintptr_t { | ||
child := ptrToNode(ptr).Children[int(index)] | ||
return nodeToPtr(child) | ||
} | ||
|
||
//export goGetRolesSize | ||
func goGetRolesSize(ptr C.uintptr_t) C.int { | ||
return C.int(len(ptrToNode(ptr).Roles)) | ||
} | ||
|
||
//export goGetRole | ||
func goGetRole(ptr C.uintptr_t, index C.int) C.uint16_t { | ||
role := ptrToNode(ptr).Roles[int(index)] | ||
return C.uint16_t(role) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package bblfsh | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"io/ioutil" | ||
"path" | ||
|
||
"github.com/bblfsh/sdk/protocol" | ||
) | ||
|
||
// ParseRequest is a placeholder for the parse requests performed by the library | ||
type ParseRequest struct { | ||
internal protocol.ParseRequest | ||
client *BblfshClient | ||
err error | ||
} | ||
|
||
// Language sets the language of the given source file to parse. | ||
func (req *ParseRequest) Language(lan string) *ParseRequest { | ||
req.internal.Language = lan | ||
return req | ||
} | ||
|
||
// ReadFile loads a file given a local path and sets the content and the filename of the request. | ||
func (req *ParseRequest) ReadFile(filepath string) *ParseRequest { | ||
data, err := ioutil.ReadFile(filepath) | ||
if err != nil { | ||
req.err = err | ||
} else { | ||
req.internal.Content = string(data) | ||
req.internal.Filename = path.Base(filepath) | ||
} | ||
return req | ||
} | ||
|
||
// Content sets the content of the parse request. It should be the source code that wants to be parsed. | ||
func (req *ParseRequest) Content(content string) *ParseRequest { | ||
req.internal.Content = content | ||
return req | ||
} | ||
|
||
// Filename sets the filename of the content. | ||
func (req *ParseRequest) Filename(filename string) *ParseRequest { | ||
req.internal.Filename = filename | ||
return req | ||
} | ||
|
||
// Encoding sets the text encoding of the content. | ||
func (req *ParseRequest) Encoding(encoding protocol.Encoding) *ParseRequest { | ||
req.internal.Encoding = encoding | ||
return req | ||
} | ||
|
||
// Do performs the actual parsing by serializaing the request, | ||
// sending it to the babelfish and waiting for the response (UAST tree). | ||
func (req *ParseRequest) Do() (*protocol.ParseResponse, error) { | ||
return req.DoWithContext(context.Background()) | ||
} | ||
|
||
// DoWithContext does the same as Do(), but sopporting cancellation by the use of | ||
// Go contexts. | ||
func (req *ParseRequest) DoWithContext(ctx context.Context) (*protocol.ParseResponse, error) { | ||
if req.err != nil { | ||
return nil, req.err | ||
} | ||
if req.client == nil { | ||
return nil, errors.New("request is clientless") | ||
} | ||
return req.client.client.Parse(ctx, &req.internal) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package bblfsh | ||
|
||
import "testing" | ||
import "github.com/bblfsh/sdk/protocol" | ||
import "github.com/stretchr/testify/require" | ||
|
||
func TestParseRequestConfiguration(t *testing.T) { | ||
req := ParseRequest{} | ||
req.Filename("file.py").Language("python").Encoding(protocol.UTF8).Content("a=b+c") | ||
|
||
require.Equal(t, "file.py", req.internal.Filename) | ||
require.Equal(t, "python", req.internal.Language) | ||
require.Equal(t, protocol.UTF8, req.internal.Encoding) | ||
require.Equal(t, "a=b+c", req.internal.Content) | ||
} | ||
|
||
func TestParseRequestClientlessError(t *testing.T) { | ||
req := ParseRequest{} | ||
_, err := req.Content("a=b+c").Language("python").Do() | ||
require.Errorf(t, err, "request is clientless") | ||
} | ||
|
||
func TestParseRequestReadFileError(t *testing.T) { | ||
req := ParseRequest{} | ||
_, err := req.ReadFile("NO_EXISTS").Do() | ||
require.Errorf(t, err, "open NO_EXISTS: no such file or directory") | ||
} |