From 28a779d6a0b4b84f8bf81390198d7d0f619557b9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 1 Jun 2020 13:28:52 -0700 Subject: [PATCH] wasm: Allow arbitrary predicates in queries --- lib/binding_web/binding.js | 27 +++++++++++----- lib/binding_web/test/query-test.js | 49 ++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/lib/binding_web/binding.js b/lib/binding_web/binding.js index eb6d4d8a..567b7eb3 100644 --- a/lib/binding_web/binding.js +++ b/lib/binding_web/binding.js @@ -44,6 +44,8 @@ class Parser { delete() { C._ts_parser_delete(this[0]); C._free(this[1]); + this[0] = 0; + this[1] = 0; } setLanguage(language) { @@ -163,6 +165,7 @@ class Tree { delete() { C._ts_tree_delete(this[0]); + this[0] = 0; } edit(edit) { @@ -506,6 +509,7 @@ class TreeCursor { delete() { marshalTreeCursor(this); C._ts_tree_cursor_delete_wasm(this.tree[0]); + this[0] = this[1] = this[2] = 0; } reset(node) { @@ -716,6 +720,7 @@ class Language { const assertedProperties = new Array(patternCount); const refutedProperties = new Array(patternCount); const predicates = new Array(patternCount); + const textPredicates = new Array(patternCount); for (let i = 0; i < patternCount; i++) { const predicatesAddress = C._ts_query_predicates_for_pattern( address, @@ -725,6 +730,7 @@ class Language { const stepCount = getValue(TRANSFER_BUFFER, 'i32'); predicates[i] = []; + textPredicates[i] = []; const steps = []; let stepAddress = predicatesAddress; @@ -756,7 +762,7 @@ class Language { if (steps[2].type === 'capture') { const captureName1 = steps[1].name; const captureName2 = steps[2].name; - predicates[i].push(function(captures) { + textPredicates[i].push(function(captures) { let node1, node2 for (const c of captures) { if (c.name === captureName1) node1 = c.node; @@ -767,7 +773,7 @@ class Language { } else { const captureName = steps[1].name; const stringValue = steps[2].value; - predicates[i].push(function(captures) { + textPredicates[i].push(function(captures) { for (const c of captures) { if (c.name === captureName) { return (c.node.text === stringValue) === isPositive; @@ -790,7 +796,7 @@ class Language { ); const captureName = steps[1].name; const regex = new RegExp(steps[2].value); - predicates[i].push(function(captures) { + textPredicates[i].push(function(captures) { for (const c of captures) { if (c.name === captureName) return regex.test(c.node.text); } @@ -823,7 +829,7 @@ class Language { break; default: - throw new Error(`Unknown query predicate \`#${steps[0].value}\``); + predicates[i].push({operator, operands: steps.slice(1)}); } steps.length = 0; @@ -840,6 +846,7 @@ class Language { INTERNAL, address, captureNames, + textPredicates, predicates, Object.freeze(setProperties), Object.freeze(assertedProperties), @@ -888,12 +895,13 @@ class Language { class Query { constructor( - internal, address, captureNames, predicates, + internal, address, captureNames, textPredicates, predicates, setProperties, assertedProperties, refutedProperties ) { assertInternal(internal); this[0] = address; this.captureNames = captureNames; + this.textPredicates = textPredicates; this.predicates = predicates; this.setProperties = setProperties; this.assertedProperties = assertedProperties; @@ -902,6 +910,7 @@ class Query { delete() { C._ts_query_delete(this[0]); + this[0] = 0; } matches(node, startPosition, endPosition) { @@ -932,7 +941,7 @@ class Query { const captures = new Array(captureCount); address = unmarshalCaptures(this, node.tree, address, captures); - if (this.predicates[pattern].every(p => p(captures))) { + if (this.textPredicates[pattern].every(p => p(captures))) { result[i] = {pattern, captures}; const setProperties = this.setProperties[pattern]; if (setProperties) result[i].setProperties = setProperties; @@ -979,7 +988,7 @@ class Query { captures.length = captureCount address = unmarshalCaptures(this, node.tree, address, captures); - if (this.predicates[pattern].every(p => p(captures))) { + if (this.textPredicates[pattern].every(p => p(captures))) { const capture = captures[captureIndex]; const setProperties = this.setProperties[pattern]; if (setProperties) capture.setProperties = setProperties; @@ -994,6 +1003,10 @@ class Query { C._free(startAddress); return result; } + + predicatesForPattern(patternIndex) { + return this.predicates[patternIndex] + } } function getText(tree, startIndex, endIndex) { diff --git a/lib/binding_web/test/query-test.js b/lib/binding_web/test/query-test.js index 8683214a..9dda9834 100644 --- a/lib/binding_web/test/query-test.js +++ b/lib/binding_web/test/query-test.js @@ -45,9 +45,6 @@ describe("Query", () => { assert.throws(() => { JavaScript.query("((identifier) @a (eq? @a @a @a))"); }, "Wrong number of arguments to `#eq?` predicate. Expected 2, got 3"); - assert.throws(() => { - JavaScript.query("((identifier) @a (#something-else? @a))"); - }, "Unknown query predicate `#something-else?`"); }); }); @@ -207,6 +204,52 @@ describe("Query", () => { ]); }); }); + + describe(".predicatesForPattern(index)", () => { + it("returns all of the predicates as objects", () => { + query = JavaScript.query(` + ( + (binary_expression + left: (identifier) @a + right: (identifier) @b) + (#something? @a @b) + (#match? @a "c") + (#something-else? @a "A" @b "B") + ) + + ((identifier) @c + (#hello! @c)) + + "if" @d + `); + + assert.deepEqual(query.predicatesForPattern(0), [ + { + operator: "something?", + operands: [ + { type: "capture", name: "a" }, + { type: "capture", name: "b" }, + ], + }, + { + operator: "something-else?", + operands: [ + { type: "capture", name: "a" }, + { type: "string", value: "A" }, + { type: "capture", name: "b" }, + { type: "string", value: "B" }, + ], + }, + ]); + assert.deepEqual(query.predicatesForPattern(1), [ + { + operator: "hello!", + operands: [{ type: "capture", name: "c" }], + }, + ]); + assert.deepEqual(query.predicatesForPattern(2), []); + }); + }); }); function formatMatches(matches) {