Start work on wasm binding to query API
This commit is contained in:
parent
fe7c74e7aa
commit
52cda5f541
4 changed files with 198 additions and 12 deletions
|
|
@ -566,3 +566,44 @@ int ts_node_is_missing_wasm(const TSTree *tree) {
|
||||||
TSNode node = unmarshal_node(tree);
|
TSNode node = unmarshal_node(tree);
|
||||||
return ts_node_is_missing(node);
|
return ts_node_is_missing(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/******************/
|
||||||
|
/* Section - Query */
|
||||||
|
/******************/
|
||||||
|
|
||||||
|
void ts_query_exec_wasm(
|
||||||
|
const TSQuery *self,
|
||||||
|
TSQueryContext *context,
|
||||||
|
const TSTree *tree
|
||||||
|
) {
|
||||||
|
TSNode node = unmarshal_node(tree);
|
||||||
|
|
||||||
|
Array(const void *) result = array_new();
|
||||||
|
|
||||||
|
unsigned index = 0;
|
||||||
|
unsigned match_count = 0;
|
||||||
|
ts_query_context_exec(context, node);
|
||||||
|
while (ts_query_context_next(context)) {
|
||||||
|
match_count++;
|
||||||
|
uint32_t pattern_index = ts_query_context_matched_pattern_index(context);
|
||||||
|
uint32_t capture_count;
|
||||||
|
const TSQueryCapture *captures = ts_query_context_matched_captures(
|
||||||
|
context,
|
||||||
|
&capture_count
|
||||||
|
);
|
||||||
|
|
||||||
|
array_grow_by(&result, 1 + 6 * capture_count);
|
||||||
|
|
||||||
|
result.contents[index++] = (const void *)pattern_index;
|
||||||
|
result.contents[index++] = (const void *)capture_count;
|
||||||
|
for (unsigned i = 0; i < capture_count; i++) {
|
||||||
|
const TSQueryCapture *capture = &captures[i];
|
||||||
|
result.contents[index++] = (const void *)capture->index;
|
||||||
|
marshal_node(result.contents + index, capture->node);
|
||||||
|
index += 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TRANSFER_BUFFER[0] = (const void *)(match_count);
|
||||||
|
TRANSFER_BUFFER[1] = result.contents;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ const SIZE_OF_NODE = 5 * SIZE_OF_INT;
|
||||||
const SIZE_OF_POINT = 2 * SIZE_OF_INT;
|
const SIZE_OF_POINT = 2 * SIZE_OF_INT;
|
||||||
const SIZE_OF_RANGE = 2 * SIZE_OF_INT + 2 * SIZE_OF_POINT;
|
const SIZE_OF_RANGE = 2 * SIZE_OF_INT + 2 * SIZE_OF_POINT;
|
||||||
const ZERO_POINT = {row: 0, column: 0};
|
const ZERO_POINT = {row: 0, column: 0};
|
||||||
|
const QUERY_WORD_REGEX = /[\w-.]*/;
|
||||||
|
|
||||||
var VERSION;
|
var VERSION;
|
||||||
var MIN_COMPATIBLE_VERSION;
|
var MIN_COMPATIBLE_VERSION;
|
||||||
|
|
@ -143,9 +144,7 @@ class Parser {
|
||||||
|
|
||||||
class Tree {
|
class Tree {
|
||||||
constructor(internal, address, language, textCallback) {
|
constructor(internal, address, language, textCallback) {
|
||||||
if (internal !== INTERNAL) {
|
assertInternal(internal);
|
||||||
throw new Error('Illegal constructor')
|
|
||||||
}
|
|
||||||
this[0] = address;
|
this[0] = address;
|
||||||
this.language = language;
|
this.language = language;
|
||||||
this.textCallback = textCallback;
|
this.textCallback = textCallback;
|
||||||
|
|
@ -201,9 +200,7 @@ class Tree {
|
||||||
|
|
||||||
class Node {
|
class Node {
|
||||||
constructor(internal, tree) {
|
constructor(internal, tree) {
|
||||||
if (internal !== INTERNAL) {
|
assertInternal(internal);
|
||||||
throw new Error('Illegal constructor')
|
|
||||||
}
|
|
||||||
this.tree = tree;
|
this.tree = tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -526,9 +523,7 @@ class Node {
|
||||||
|
|
||||||
class TreeCursor {
|
class TreeCursor {
|
||||||
constructor(internal, tree) {
|
constructor(internal, tree) {
|
||||||
if (internal !== INTERNAL) {
|
assertInternal(internal);
|
||||||
throw new Error('Illegal constructor')
|
|
||||||
}
|
|
||||||
this.tree = tree;
|
this.tree = tree;
|
||||||
unmarshalTreeCursor(this);
|
unmarshalTreeCursor(this);
|
||||||
}
|
}
|
||||||
|
|
@ -630,9 +625,7 @@ class TreeCursor {
|
||||||
|
|
||||||
class Language {
|
class Language {
|
||||||
constructor(internal, address) {
|
constructor(internal, address) {
|
||||||
if (internal !== INTERNAL) {
|
assertInternal(internal);
|
||||||
throw new Error('Illegal constructor')
|
|
||||||
}
|
|
||||||
this[0] = address;
|
this[0] = address;
|
||||||
this.types = new Array(C._ts_language_symbol_count(this[0]));
|
this.types = new Array(C._ts_language_symbol_count(this[0]));
|
||||||
for (let i = 0, n = this.types.length; i < n; i++) {
|
for (let i = 0, n = this.types.length; i < n; i++) {
|
||||||
|
|
@ -672,6 +665,51 @@ class Language {
|
||||||
return this.fields[fieldName] || null;
|
return this.fields[fieldName] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query(source) {
|
||||||
|
const sourceLength = lengthBytesUTF8(source);
|
||||||
|
const sourceAddress = C._malloc(sourceLength + 1);
|
||||||
|
stringToUTF8(source, sourceAddress, sourceLength + 1);
|
||||||
|
const address = C._ts_query_new(
|
||||||
|
this[0],
|
||||||
|
sourceAddress,
|
||||||
|
sourceLength,
|
||||||
|
TRANSFER_BUFFER,
|
||||||
|
TRANSFER_BUFFER + SIZE_OF_INT
|
||||||
|
);
|
||||||
|
if (address) {
|
||||||
|
const contextAddress = C._ts_query_context_new(address);
|
||||||
|
const captureCount = C._ts_query_capture_count(address);
|
||||||
|
const captureNames = new Array(captureCount);
|
||||||
|
for (let i = 0; i < captureCount; i++) {
|
||||||
|
const nameAddress = C._ts_query_capture_name_for_id(
|
||||||
|
address,
|
||||||
|
i,
|
||||||
|
TRANSFER_BUFFER
|
||||||
|
);
|
||||||
|
const nameLength = getValue(TRANSFER_BUFFER, 'i32');
|
||||||
|
captureNames[i] = UTF8ToString(nameAddress, nameLength);
|
||||||
|
}
|
||||||
|
return new Query(INTERNAL, address, contextAddress, captureNames);
|
||||||
|
} else {
|
||||||
|
const errorId = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
|
||||||
|
const utf8ErrorOffset = getValue(TRANSFER_BUFFER, 'i32');
|
||||||
|
const errorOffset = UTF8ToString(sourceAddress, utf8ErrorOffset).length;
|
||||||
|
C._free(sourceAddress);
|
||||||
|
const suffix = source.slice(errorOffset, 100);
|
||||||
|
switch (errorId) {
|
||||||
|
case 2: throw new RangeError(
|
||||||
|
`Bad node name '${suffix.match(QUERY_WORD_REGEX)[0]}'`
|
||||||
|
);
|
||||||
|
case 3: throw new RangeError(
|
||||||
|
`Bad field name '${suffix.match(QUERY_WORD_REGEX)[0]}'`
|
||||||
|
);
|
||||||
|
default: throw new SyntaxError(
|
||||||
|
`Bad syntax at offset ${errorOffset}: '${suffix}'...`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static load(url) {
|
static load(url) {
|
||||||
let bytes;
|
let bytes;
|
||||||
if (
|
if (
|
||||||
|
|
@ -704,6 +742,55 @@ class Language {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Query {
|
||||||
|
constructor(internal, address, contextAddress, captureNames) {
|
||||||
|
assertInternal(internal);
|
||||||
|
this[0] = address;
|
||||||
|
this[1] = contextAddress;
|
||||||
|
this.captureNames = captureNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete() {
|
||||||
|
C._ts_query_delete(this[0]);
|
||||||
|
C._ts_query_context_delete(this[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
exec(queryNode) {
|
||||||
|
marshalNode(queryNode);
|
||||||
|
|
||||||
|
C._ts_query_exec_wasm(this[0], this[1], queryNode.tree[0]);
|
||||||
|
|
||||||
|
const matchCount = getValue(TRANSFER_BUFFER, 'i32');
|
||||||
|
const nodesAddress = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
|
||||||
|
const result = new Array(matchCount);
|
||||||
|
|
||||||
|
let address = nodesAddress;
|
||||||
|
for (let i = 0; i < matchCount; i++) {
|
||||||
|
const pattern = getValue(address, 'i32');
|
||||||
|
address += SIZE_OF_INT;
|
||||||
|
const captures = new Array(getValue(address, 'i32'));
|
||||||
|
address += SIZE_OF_INT;
|
||||||
|
for (let j = 0, n = captures.length; j < n; j++) {
|
||||||
|
const captureIndex = getValue(address, 'i32');
|
||||||
|
address += SIZE_OF_INT;
|
||||||
|
const node = unmarshalNode(queryNode.tree, address);
|
||||||
|
address += SIZE_OF_NODE;
|
||||||
|
captures[j] = {name: this.captureNames[captureIndex], node};
|
||||||
|
}
|
||||||
|
result[i] = {pattern, captures};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the intermediate buffers
|
||||||
|
C._free(nodesAddress);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertInternal(x) {
|
||||||
|
if (x !== INTERNAL) throw new Error('Illegal constructor')
|
||||||
|
}
|
||||||
|
|
||||||
function isPoint(point) {
|
function isPoint(point) {
|
||||||
return (
|
return (
|
||||||
point &&
|
point &&
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,13 @@
|
||||||
"_ts_parser_new_wasm",
|
"_ts_parser_new_wasm",
|
||||||
"_ts_parser_parse_wasm",
|
"_ts_parser_parse_wasm",
|
||||||
"_ts_parser_set_language",
|
"_ts_parser_set_language",
|
||||||
|
"_ts_query_capture_count",
|
||||||
|
"_ts_query_capture_name_for_id",
|
||||||
|
"_ts_query_context_delete",
|
||||||
|
"_ts_query_context_new",
|
||||||
|
"_ts_query_delete",
|
||||||
|
"_ts_query_exec_wasm",
|
||||||
|
"_ts_query_new",
|
||||||
"_ts_tree_cursor_current_field_id_wasm",
|
"_ts_tree_cursor_current_field_id_wasm",
|
||||||
"_ts_tree_cursor_current_node_id_wasm",
|
"_ts_tree_cursor_current_node_id_wasm",
|
||||||
"_ts_tree_cursor_current_node_is_missing_wasm",
|
"_ts_tree_cursor_current_node_is_missing_wasm",
|
||||||
|
|
|
||||||
51
lib/binding_web/test/query-test.js
Normal file
51
lib/binding_web/test/query-test.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
const {assert} = require('chai');
|
||||||
|
let Parser, JavaScript;
|
||||||
|
|
||||||
|
describe("Query", () => {
|
||||||
|
let parser, tree, query;
|
||||||
|
|
||||||
|
before(async () =>
|
||||||
|
({Parser, JavaScript} = await require('./helper'))
|
||||||
|
);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
parser = new Parser().setLanguage(JavaScript);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
parser.delete();
|
||||||
|
if (tree) tree.delete();
|
||||||
|
if (query) query.delete();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error on invalid syntax', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
JavaScript.query("(function_declaration wat)")
|
||||||
|
}, "Bad syntax at offset 22: \'wat)\'...");
|
||||||
|
assert.throws(() => {
|
||||||
|
JavaScript.query("(non_existent)")
|
||||||
|
}, "Bad node name 'non_existent'");
|
||||||
|
assert.throws(() => {
|
||||||
|
JavaScript.query("(function_declaration non_existent:(identifier))")
|
||||||
|
}, "Bad field name 'non_existent'");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('matches simple queries', () => {
|
||||||
|
tree = parser.parse("function one() { two(); function three() {} }");
|
||||||
|
const query = JavaScript.query(`
|
||||||
|
(function_declaration name:(identifier) @the-name)
|
||||||
|
`);
|
||||||
|
const matches = query.exec(tree.rootNode);
|
||||||
|
assert.deepEqual(
|
||||||
|
matches.map(({pattern, captures}) => ({
|
||||||
|
pattern,
|
||||||
|
captures: captures.map(({name, node}) => ({name, text: node.text}))
|
||||||
|
})),
|
||||||
|
[
|
||||||
|
{pattern: 0, captures: [{name: 'the-name', text: 'one'}]},
|
||||||
|
// {pattern: 0, captures: [{name: 'the-function', text: 'two'}]},
|
||||||
|
{pattern: 0, captures: [{name: 'the-name', text: 'three'}]},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue