Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
012adde
Bump tree-sitter for new EOF API
maxbrunsfeld Nov 4, 2019
dc57fe6
0.15.14
maxbrunsfeld Nov 4, 2019
8240213
:arrow_up: tree-sitter for symbol deduping
maxbrunsfeld Dec 6, 2019
07c7100
0.15.15
maxbrunsfeld Dec 6, 2019
048795d
:arrow_up: tree-sitter for null character bugfixes
maxbrunsfeld Dec 7, 2019
a663107
0.16.0
maxbrunsfeld Dec 7, 2019
3f8dcb2
Bump tree-sitter library
maxbrunsfeld Apr 3, 2020
ee143c8
0.16.1
maxbrunsfeld Apr 3, 2020
adebd0d
Fix the TypeScript definition of a Range (#64)
thibaultdalban May 20, 2020
c40d598
Add bufferSize and includedRanges options to parse function in TS def…
thibaultdalban May 20, 2020
a95bc5c
Implement Query API (#62)
romgrk Aug 18, 2020
1a5459d
Bump tree-sitter
maxbrunsfeld Aug 18, 2020
d395d3d
:arrow_up: superstring
maxbrunsfeld Aug 18, 2020
c520302
0.16.2
maxbrunsfeld Aug 18, 2020
e41c14b
Update tree-sitter library
maxbrunsfeld Sep 23, 2020
209e734
0.17.0
maxbrunsfeld Sep 23, 2020
376fc5b
Bump tree-sitter
maxbrunsfeld Oct 12, 2020
25ae24f
0.17.1
maxbrunsfeld Oct 12, 2020
73b94a8
:arrow_up: tree-sitter
maxbrunsfeld Feb 19, 2021
9658df8
0.17.2
maxbrunsfeld Feb 19, 2021
e867853
0.18.0
maxbrunsfeld Feb 19, 2021
d0133d4
Add a string representation of TSQueryErrorStructure
maxbrunsfeld Feb 19, 2021
812cd15
0.18.1
maxbrunsfeld Feb 19, 2021
738bb12
Added missing ts tree-sitter.d.ts declarations (#80)
ahlinc Mar 3, 2021
c5f3dff
:arrow_up: tree-sitter lib to 0.19
maxbrunsfeld Mar 8, 2021
7785d1c
0.19.0
maxbrunsfeld Mar 8, 2021
5054899
fix: prevent Nan::Utf8String destructor call due to out of scope, fix…
ahlinc Mar 7, 2021
6b876b7
Update prebuild, prebuild-install to use newer node-gyp that uses pyt…
ahlinc Mar 8, 2021
596b945
Update chai and mocha to latest version to resolve npm complains
ahlinc Mar 8, 2021
964c797
Merge pull request #81 from ahlinc/fix/segmentation-fault/72
maxbrunsfeld Mar 8, 2021
ccd2c95
fix #82: Update compiler flag '-std=c++17'
anqur Sep 1, 2021
16302f9
Merge pull request #92 from anqurvanillapy/master
maxbrunsfeld Sep 15, 2021
e197a73
Upgrade tree-sitter library
maxbrunsfeld Oct 11, 2021
fc99954
0.20.0
maxbrunsfeld Oct 11, 2021
a4c6fc8
Convert module to use NAPI instead of Nan
maxbrunsfeld Nov 4, 2019
d219bbe
first pass at converting query from nan to napi
MichaelBelousov Mar 6, 2022
a98f61b
repair rebase and finish first draft of query api conversion to napi
MichaelBelousov Mar 6, 2022
6881821
fix hidden method names that I incorrectly fixed
MichaelBelousov Mar 6, 2022
f9d263c
add loading of napi-compiled language native addons
MichaelBelousov Mar 6, 2022
4648650
fix query impl rebase
MichaelBelousov Mar 6, 2022
efa2c04
suppress destruct of static function refs
MichaelBelousov Mar 6, 2022
9115155
fix passing stderr, the FILE*, to an api that expects the file descri…
MichaelBelousov Mar 9, 2023
6f37259
disable exceptions in binding.gyp and tests pass (but segfaults on cl…
MichaelBelousov Mar 10, 2023
09aa68b
suppress destruction of static Napi::ObjectReference
MichaelBelousov Mar 10, 2023
d4affe7
Merge branch 'napi' into napi-with-query-api
MichaelBelousov Mar 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"<!(node -p \"require('node-addon-api').gyp\")"
],
"defines": [
"NAPI_DISABLE_CPP_EXCEPTIONS=",
"NAPI_DISABLE_CPP_EXCEPTIONS",
],
"sources": [
"src/binding.cc",
Expand All @@ -16,8 +16,10 @@
"src/logger.cc",
"src/node.cc",
"src/parser.cc",
"src/query.cc",
"src/tree.cc",
"src/tree_cursor.cc",
"src/util.cc",
],
"include_dirs": [
"vendor/tree-sitter/lib/include",
Expand All @@ -32,9 +34,9 @@
},
}]
],
'xcode_settings': {
'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',
},
"cflags": [
"-std=c++17",
],
},
{
"target_name": "tree_sitter",
Expand Down
296 changes: 278 additions & 18 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ try {
}
}

const vm = require('vm');
const util = require('util')
const {Parser, NodeMethods, Tree, TreeCursor} = binding;
const {Query, Parser, NodeMethods, Tree, TreeCursor} = binding;

/*
* Tree
*/

const {rootNode, edit} = Tree.prototype;

Expand All @@ -37,6 +40,10 @@ Tree.prototype.walk = function() {
return this.rootNode.walk()
};

/*
* Node
*/

class SyntaxNode {
constructor(tree) {
this.tree = tree;
Expand Down Expand Up @@ -241,6 +248,10 @@ class SyntaxNode {
}
}

/*
* Parser
*/

const {parse, parseTextBuffer, parseTextBufferSync, setLanguage} = Parser.prototype;
const languageSymbol = Symbol('parser.language');

Expand Down Expand Up @@ -326,6 +337,10 @@ Parser.prototype.parseTextBufferSync = function(buffer, oldTree, {includedRanges
return tree;
};

/*
* TreeCursor
*/

const {startPosition, endPosition, currentNode, reset} = TreeCursor.prototype;

Object.defineProperties(TreeCursor.prototype, {
Expand Down Expand Up @@ -358,6 +373,225 @@ TreeCursor.prototype.reset = function(node) {
reset.call(this);
}

/*
* Query
*/

const {_matches, _captures} = Query.prototype;

const PREDICATE_STEP_TYPE = {
DONE: 0,
CAPTURE: 1,
STRING: 2,
}

const ZERO_POINT = { row: 0, column: 0 };

Query.prototype._init = function() {
/*
* Initialize predicate functions
* format: [type1, value1, type2, value2, ...]
*/
const predicateDescriptions = this._getPredicates();
const patternCount = predicateDescriptions.length;

const setProperties = new Array(patternCount);
const assertedProperties = new Array(patternCount);
const refutedProperties = new Array(patternCount);
const predicates = new Array(patternCount);

const FIRST = 0
const SECOND = 2
const THIRD = 4

for (let i = 0; i < predicateDescriptions.length; i++) {
predicates[i] = [];

for (let j = 0; j < predicateDescriptions[i].length; j++) {

const steps = predicateDescriptions[i][j];
const stepsLength = steps.length / 2;

if (steps[FIRST] !== PREDICATE_STEP_TYPE.STRING) {
throw new Error('Predicates must begin with a literal value');
}

const operator = steps[FIRST + 1];

let isPositive = true;

switch (operator) {
case 'not-eq?':
isPositive = false;
case 'eq?':
if (stepsLength !== 3) throw new Error(
`Wrong number of arguments to \`#eq?\` predicate. Expected 2, got ${stepsLength - 1}`
);
if (steps[SECOND] !== PREDICATE_STEP_TYPE.CAPTURE) throw new Error(
`First argument of \`#eq?\` predicate must be a capture. Got "${steps[SECOND + 1]}"`
);
if (steps[THIRD] === PREDICATE_STEP_TYPE.CAPTURE) {
const captureName1 = steps[SECOND + 1];
const captureName2 = steps[THIRD + 1];
predicates[i].push(function(captures) {
let node1, node2
for (const c of captures) {
if (c.name === captureName1) node1 = c.node;
if (c.name === captureName2) node2 = c.node;
}
return (node1.text === node2.text) === isPositive;
});
} else {
const captureName = steps[SECOND + 1];
const stringValue = steps[THIRD + 1];
predicates[i].push(function(captures) {
for (const c of captures) {
if (c.name === captureName) {
return (c.node.text === stringValue) === isPositive;
};
}
return false;
});
}
break;

case 'match?':
if (stepsLength !== 3) throw new Error(
`Wrong number of arguments to \`#match?\` predicate. Expected 2, got ${stepsLength - 1}.`
);
if (steps[SECOND] !== PREDICATE_STEP_TYPE.CAPTURE) throw new Error(
`First argument of \`#match?\` predicate must be a capture. Got "${steps[SECOND + 1]}".`
);
if (steps[THIRD] !== PREDICATE_STEP_TYPE.STRING) throw new Error(
`Second argument of \`#match?\` predicate must be a string. Got @${steps[THIRD + 1]}.`
);
const captureName = steps[SECOND + 1];
const regex = new RegExp(steps[THIRD + 1]);
predicates[i].push(function(captures) {
for (const c of captures) {
if (c.name === captureName) return regex.test(c.node.text);
}
return false;
});
break;

case 'set!':
if (stepsLength < 2 || stepsLength > 3) throw new Error(
`Wrong number of arguments to \`#set!\` predicate. Expected 1 or 2. Got ${stepsLength - 1}.`
);
if (steps.some((s, i) => (i % 2 !== 1) && s !== PREDICATE_STEP_TYPE.STRING)) throw new Error(
`Arguments to \`#set!\` predicate must be a strings.".`
);
if (!setProperties[i]) setProperties[i] = {};
setProperties[i][steps[SECOND + 1]] = steps[THIRD] ? steps[THIRD + 1] : null;
break;

case 'is?':
case 'is-not?':
if (stepsLength < 2 || stepsLength > 3) throw new Error(
`Wrong number of arguments to \`#${operator}\` predicate. Expected 1 or 2. Got ${stepsLength - 1}.`
);
if (steps.some((s, i) => (i % 2 !== 1) && s !== PREDICATE_STEP_TYPE.STRING)) throw new Error(
`Arguments to \`#${operator}\` predicate must be a strings.".`
);
const properties = operator === 'is?' ? assertedProperties : refutedProperties;
if (!properties[i]) properties[i] = {};
properties[i][steps[SECOND + 1]] = steps[THIRD] ? steps[THIRD + 1] : null;
break;

default:
throw new Error(`Unknown query predicate \`#${steps[FIRST + 1]}\``);
}
}
}

this.predicates = Object.freeze(predicates);
this.setProperties = Object.freeze(setProperties);
this.assertedProperties = Object.freeze(assertedProperties);
this.refutedProperties = Object.freeze(refutedProperties);
}

Query.prototype.matches = function(rootNode, startPosition = ZERO_POINT, endPosition = ZERO_POINT) {
marshalNode(rootNode);
const [returnedMatches, returnedNodes] = _matches.call(this, rootNode.tree,
startPosition.row, startPosition.column,
endPosition.row, endPosition.column
);
const nodes = unmarshalNodes(returnedNodes, rootNode.tree);
const results = [];

let i = 0
let nodeIndex = 0;
while (i < returnedMatches.length) {
const patternIndex = returnedMatches[i++];
const captures = [];

while (i < returnedMatches.length && typeof returnedMatches[i] === 'string') {
const captureName = returnedMatches[i++];
captures.push({
name: captureName,
node: nodes[nodeIndex++],
})
}

if (this.predicates[patternIndex].every(p => p(captures))) {
const result = {pattern: patternIndex, captures};
const setProperties = this.setProperties[patternIndex];
const assertedProperties = this.assertedProperties[patternIndex];
const refutedProperties = this.refutedProperties[patternIndex];
if (setProperties) result.setProperties = setProperties;
if (assertedProperties) result.assertedProperties = assertedProperties;
if (refutedProperties) result.refutedProperties = refutedProperties;
results.push(result);
}
}

return results;
}

Query.prototype.captures = function(rootNode, startPosition = ZERO_POINT, endPosition = ZERO_POINT) {
marshalNode(rootNode);
const [returnedMatches, returnedNodes] = _captures.call(this, rootNode.tree,
startPosition.row, startPosition.column,
endPosition.row, endPosition.column
);
const nodes = unmarshalNodes(returnedNodes, rootNode.tree);
const results = [];

let i = 0
let nodeIndex = 0;
while (i < returnedMatches.length) {
const patternIndex = returnedMatches[i++];
const captureIndex = returnedMatches[i++];
const captures = [];

while (i < returnedMatches.length && typeof returnedMatches[i] === 'string') {
const captureName = returnedMatches[i++];
captures.push({
name: captureName,
node: nodes[nodeIndex++],
})
}

if (this.predicates[patternIndex].every(p => p(captures))) {
const result = captures[captureIndex];
const setProperties = this.setProperties[patternIndex];
const assertedProperties = this.assertedProperties[patternIndex];
const refutedProperties = this.refutedProperties[patternIndex];
if (setProperties) result.setProperties = setProperties;
if (assertedProperties) result.assertedProperties = assertedProperties;
if (refutedProperties) result.refutedProperties = refutedProperties;
results.push(result);
}
}

return results;
}

/*
* Other functions
*/

function getTextFromString (node) {
return this.input.substring(node.startIndex, node.endIndex);
}
Expand All @@ -382,37 +616,62 @@ const {pointTransferArray} = binding;
const NODE_FIELD_COUNT = 6;
const ERROR_TYPE_ID = 0xFFFF

function unmarshalNode(value, tree, offset = 0) {
function getID(buffer, offset) {
const low = BigInt(buffer[offset]);
const high = BigInt(buffer[offset + 1]);
return (high << 32n) + low;
}

function unmarshalNode(value, tree, offset = 0, cache = null) {
/* case 1: node from the tree cache */
if (typeof value === 'object') {
const node = value;
return node;
} else {
const nodeTypeId = value;
const NodeClass = nodeTypeId === ERROR_TYPE_ID
? SyntaxNode
: tree.language.nodeSubclasses[nodeTypeId];
const {nodeTransferArray} = binding;
if (nodeTransferArray[0] || nodeTransferArray[1]) {
const result = new NodeClass(tree);
for (let i = 0; i < NODE_FIELD_COUNT; i++) {
result[i] = nodeTransferArray[offset + i];
}
tree._cacheNode(result);
return result;
}
}

/* case 2: node being transferred */
const nodeTypeId = value;
const NodeClass = nodeTypeId === ERROR_TYPE_ID
? SyntaxNode
: tree.language.nodeSubclasses[nodeTypeId];

const {nodeTransferArray} = binding;
const id = getID(nodeTransferArray, offset)
if (id === 0n) {
return null
}

let cachedResult;
if (cache && (cachedResult = cache.get(id)))
return cachedResult;

const result = new NodeClass(tree);
for (let i = 0; i < NODE_FIELD_COUNT; i++) {
result[i] = nodeTransferArray[offset + i];
}

if (cache)
cache.set(id, result);
else
tree._cacheNode(result);

return result;
}

function unmarshalNodes(nodes, tree) {
const cache = new Map();

let offset = 0;
for (let i = 0, {length} = nodes; i < length; i++) {
const node = unmarshalNode(nodes[i], tree, offset);
const node = unmarshalNode(nodes[i], tree, offset, cache);
if (node !== nodes[i]) {
nodes[i] = node;
offset += NODE_FIELD_COUNT
}
}

tree._cacheNodes(Array.from(cache.values()));

return nodes;
}

Expand Down Expand Up @@ -491,6 +750,7 @@ function camelCase(name, upperCase) {
}

module.exports = Parser;
module.exports.Query = Query;
module.exports.Tree = Tree;
module.exports.SyntaxNode = SyntaxNode;
module.exports.TreeCursor = TreeCursor;
Loading