From c47e217e7303c704d2cb88ced1b2720f15a2d949 Mon Sep 17 00:00:00 2001 From: Daumantas Kavolis Date: Wed, 17 May 2023 10:39:37 +0300 Subject: [PATCH] API extensions --- lib/binding_rust/bindings.rs | 77 ++++++++++++++- lib/binding_rust/ffi.rs | 20 +++- lib/binding_rust/lib.rs | 126 ++++++++++++++++++++++++ lib/binding_web/binding.c | 37 +++++++ lib/binding_web/binding.js | 97 +++++++++++++++++++ lib/binding_web/exports.json | 16 ++- lib/binding_web/test/node-test.js | 33 +++++++ lib/binding_web/tree-sitter-web.d.ts | 20 ++++ lib/include/tree_sitter/api.h | 104 +++++++++++++++++++- lib/include/tree_sitter/parser.h | 2 + lib/src/language.c | 66 +++++++++++++ lib/src/language.h | 24 +---- lib/src/node.c | 9 ++ lib/src/tree_cursor.c | 139 ++++++++++++++++++++++++++- 14 files changed, 741 insertions(+), 29 deletions(-) diff --git a/lib/binding_rust/bindings.rs b/lib/binding_rust/bindings.rs index 82eec594..da870263 100644 --- a/lib/binding_rust/bindings.rs +++ b/lib/binding_rust/bindings.rs @@ -1,5 +1,6 @@ /* automatically generated by rust-bindgen 0.66.1 */ +pub type TSStateId = u16; pub type TSSymbol = u16; pub type TSFieldId = u16; #[repr(C)] @@ -27,6 +28,11 @@ pub struct TSQuery { pub struct TSQueryCursor { _unused: [u8; 0], } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct TSLookaheadIterator { + _unused: [u8; 0], +} pub const TSInputEncoding_TSInputEncodingUTF8: TSInputEncoding = 0; pub const TSInputEncoding_TSInputEncodingUTF16: TSInputEncoding = 1; pub type TSInputEncoding = ::std::os::raw::c_uint; @@ -326,6 +332,14 @@ extern "C" { #[doc = " Check if the node is a syntax error or contains any syntax errors."] pub fn ts_node_has_error(arg1: TSNode) -> bool; } +extern "C" { + #[doc = " Check if the node is a syntax error."] + pub fn ts_node_is_error(arg1: TSNode) -> bool; +} +extern "C" { + #[doc = " Get this node's parse state."] + pub fn ts_node_parse_state(arg1: TSNode) -> TSStateId; +} extern "C" { #[doc = " Get the node's immediate parent."] pub fn ts_node_parent(arg1: TSNode) -> TSNode; @@ -427,6 +441,10 @@ extern "C" { #[doc = " Re-initialize a tree cursor to start at a different node."] pub fn ts_tree_cursor_reset(arg1: *mut TSTreeCursor, arg2: TSNode); } +extern "C" { + #[doc = " Re-initialize a tree cursor to the same position as another cursor.\n\n Unlike `ts_tree_cursor_reset`, this will not lose parent information and\n allows reusing already created cursors."] + pub fn ts_tree_cursor_reset_to(arg1: *mut TSTreeCursor, arg2: *const TSTreeCursor); +} extern "C" { #[doc = " Get the tree cursor's current node."] pub fn ts_tree_cursor_current_node(arg1: *const TSTreeCursor) -> TSNode; @@ -450,9 +468,16 @@ extern "C" { pub fn ts_tree_cursor_goto_next_sibling(arg1: *mut TSTreeCursor) -> bool; } extern "C" { - #[doc = " Move the cursor to the first child of its current node.\n\n This returns `true` if the cursor successfully moved, and returns `false`\n if there were no children."] + #[doc = " Move the cursor to the previous sibling of its current node.\n\n This returns `true` if the cursor successfully moved, and returns `false` if\n there was no previous sibling node."] + pub fn ts_tree_cursor_goto_previous_sibling(arg1: *mut TSTreeCursor) -> bool; +} +extern "C" { + #[doc = " Move the cursor to the first/last child of its current node.\n\n This returns `true` if the cursor successfully moved, and returns `false`\n if there were no children."] pub fn ts_tree_cursor_goto_first_child(arg1: *mut TSTreeCursor) -> bool; } +extern "C" { + pub fn ts_tree_cursor_goto_last_child(arg1: *mut TSTreeCursor) -> bool; +} extern "C" { #[doc = " Move the cursor to the node that is the nth descendant of\n the original node that the cursor was constructed with, where\n zero represents the original node itself."] pub fn ts_tree_cursor_goto_descendant(arg1: *mut TSTreeCursor, arg2: u32); @@ -608,6 +633,10 @@ extern "C" { #[doc = " Get the number of distinct node types in the language."] pub fn ts_language_symbol_count(arg1: *const TSLanguage) -> u32; } +extern "C" { + #[doc = " Get the number of valid states in this language."] + pub fn ts_language_state_count(arg1: *const TSLanguage) -> u32; +} extern "C" { #[doc = " Get a node type string for the given numerical id."] pub fn ts_language_symbol_name( @@ -651,6 +680,52 @@ extern "C" { #[doc = " Get the ABI version number for this language. This version number is used\n to ensure that languages were generated by a compatible version of\n Tree-sitter.\n\n See also `ts_parser_set_language`."] pub fn ts_language_version(arg1: *const TSLanguage) -> u32; } +extern "C" { + #[doc = " Get the next parse state. Combine this with lookahead iterators to generate\n completion suggestions or valid symbols in error nodes."] + pub fn ts_language_next_state( + arg1: *const TSLanguage, + arg2: TSStateId, + arg3: TSSymbol, + ) -> TSStateId; +} +extern "C" { + #[doc = " Create a new lookahead iterator for the given language and parse state.\n\n This returns `NULL` if state is invalid for the language.\n\n Repeatedly using `ts_lookahead_iterator_advance` and\n `ts_lookahead_iterator_current_symbol` will generate valid symbols in the\n given parse state. Newly created lookahead iterators will contain the `ERROR`\n symbol.\n\n Lookahead iterators can be useful to generate suggestions and improve syntax\n error diagnostics. To get symbols valid in an ERROR node, use the lookahead\n iterator on its first leaf node state. For `MISSING` nodes, a lookahead\n iterator created on the previous non-extra leaf node may be appropriate."] + pub fn ts_lookahead_iterator_new( + arg1: *const TSLanguage, + arg2: TSStateId, + ) -> *mut TSLookaheadIterator; +} +extern "C" { + #[doc = " Delete a lookahead iterator freeing all the memory used."] + pub fn ts_lookahead_iterator_delete(arg1: *mut TSLookaheadIterator); +} +extern "C" { + #[doc = " Reset the lookahead iterator to another state.\n\n This returns `true` if the iterator was reset to the given state and `false`\n otherwise."] + pub fn ts_lookahead_iterator_reset_state( + arg1: *mut TSLookaheadIterator, + arg2: TSStateId, + ) -> bool; +} +extern "C" { + #[doc = " Reset the lookahead iterator.\n\n This returns `true` if the language was set successfully and `false`\n otherwise."] + pub fn ts_lookahead_iterator_reset( + arg1: *mut TSLookaheadIterator, + arg2: *const TSLanguage, + arg3: TSStateId, + ) -> bool; +} +extern "C" { + #[doc = " Get the current language of the lookahead iterator."] + pub fn ts_lookahead_iterator_language(arg1: *const TSLookaheadIterator) -> *const TSLanguage; +} +extern "C" { + #[doc = " Advance the lookahead iterator to the next symbol.\n\n This returns `true` if there is a new symbol and `false` otherwise."] + pub fn ts_lookahead_iterator_advance(arg1: *mut TSLookaheadIterator) -> bool; +} +extern "C" { + #[doc = " Get the current symbol of the lookahead iterator;"] + pub fn ts_lookahead_iterator_current_symbol(arg1: *const TSLookaheadIterator) -> TSSymbol; +} extern "C" { #[doc = " Set the allocation functions used by the library.\n\n By default, Tree-sitter uses the standard libc allocation functions,\n but aborts the process when an allocation fails. This function lets\n you supply alternative allocation functions at runtime.\n\n If you pass `NULL` for any parameter, Tree-sitter will switch back to\n its default implementation of that function.\n\n If you call this function after the library has already been used, then\n you must ensure that either:\n 1. All the existing objects have been freed.\n 2. The new allocator shares its state with the old one, so it is capable\n of freeing memory that was allocated by the old allocator."] pub fn ts_set_allocator( diff --git a/lib/binding_rust/ffi.rs b/lib/binding_rust/ffi.rs index fc872041..f1a739b1 100644 --- a/lib/binding_rust/ffi.rs +++ b/lib/binding_rust/ffi.rs @@ -8,7 +8,9 @@ extern "C" { pub(crate) fn dup(fd: std::os::raw::c_int) -> std::os::raw::c_int; } -use crate::{Language, Node, Parser, Query, QueryCursor, QueryError, Tree, TreeCursor}; +use crate::{ + Language, LookaheadIterator, Node, Parser, Query, QueryCursor, QueryError, Tree, TreeCursor, +}; use std::{marker::PhantomData, mem::ManuallyDrop, ptr::NonNull, str}; impl Language { @@ -130,3 +132,19 @@ impl QueryCursor { ManuallyDrop::new(self).ptr.as_ptr() } } + +impl<'a> LookaheadIterator<'a> { + /// Reconstructs a [LookaheadIterator] from a raw pointer. + /// + /// # Safety + /// + /// `ptr` must be non-null. + pub unsafe fn from_raw(ptr: *mut TSLookaheadIterator) -> LookaheadIterator<'a> { + LookaheadIterator(NonNull::new_unchecked(ptr), PhantomData) + } + + /// Consumes the [LookaheadIterator], returning a raw pointer to the underlying C structure. + pub fn into_raw(self) -> *mut TSLookaheadIterator { + ManuallyDrop::new(self).0.as_ptr() + } +} diff --git a/lib/binding_rust/lib.rs b/lib/binding_rust/lib.rs index 2f8323df..6a46dafc 100644 --- a/lib/binding_rust/lib.rs +++ b/lib/binding_rust/lib.rs @@ -87,6 +87,10 @@ pub struct Node<'a>(ffi::TSNode, PhantomData<&'a ()>); #[doc(alias = "TSParser")] pub struct Parser(NonNull); +/// A stateful object that is used to look up symbols valid in a specific parse state +#[doc(alias = "TSLookaheadIterator")] +pub struct LookaheadIterator<'a>(NonNull, PhantomData<&'a ()>); + /// A type of log message. #[derive(Debug, PartialEq, Eq)] pub enum LogType { @@ -269,6 +273,12 @@ impl Language { unsafe { ffi::ts_language_symbol_count(self.0) as usize } } + /// Get the number of valid states in this language. + #[doc(alias = "ts_language_state_count")] + pub fn parse_state_count(&self) -> usize { + unsafe { ffi::ts_language_state_count(self.0) as usize } + } + /// Get the name of the node kind for the given numerical id. #[doc(alias = "ts_language_symbol_name")] pub fn node_kind_for_id(&self, id: u16) -> Option<&'static str> { @@ -336,6 +346,36 @@ impl Language { }; FieldId::new(id) } + + /// Get the next parse state. Combine this with [lookahead_iterator] to + /// generate completion suggestions or valid symbols in error nodes. + #[doc(alias = "ts_language_next_state")] + pub fn next_state(&self, state: u16, id: u16) -> u16 { + unsafe { ffi::ts_language_next_state(self.0, state, id) } + } + + /// Create a new lookahead iterator for this language and parse state. + /// + /// This returns `None` if state is invalid for this language. + /// + /// Iterating [LookaheadIterator] will yield valid symbols in the given + /// parse state. Newly created lookahead iterators will return the `ERROR` + /// symbol from [LookaheadIterator::current_symbol]. + /// + /// Lookahead iterators can be useful to generate suggestions and improve + /// syntax error diagnostics. To get symbols valid in an ERROR node, use the + /// lookahead iterator on its first leaf node state. For `MISSING` nodes, a + /// lookahead iterator created on the previous non-extra leaf node may be + /// appropriate. + #[doc(alias = "ts_lookahead_iterator_new")] + pub fn lookahead_iterator<'a>(&self, state: u16) -> Option> { + let ptr = unsafe { ffi::ts_lookahead_iterator_new(self.0, state) }; + if ptr.is_null() { + None + } else { + Some(unsafe { LookaheadIterator::from_raw(ptr) }) + } + } } impl Parser { @@ -885,6 +925,12 @@ impl<'tree> Node<'tree> { self.kind_id() == u16::MAX } + /// Get this node's parse state. + #[doc(alias = "ts_node_parse_state")] + pub fn parse_state(&self) -> u16 { + unsafe { ffi::ts_node_parse_state(self.0) } + } + /// Check if this node is *missing*. /// /// Missing nodes are inserted by the parser in order to recover from certain kinds of @@ -1305,6 +1351,15 @@ impl<'a> TreeCursor<'a> { return unsafe { ffi::ts_tree_cursor_goto_first_child(&mut self.0) }; } + /// Move this cursor to the last child of its current node. + /// + /// This returns `true` if the cursor successfully moved, and returns `false` + /// if there were no children. + #[doc(alias = "ts_tree_cursor_goto_last_child")] + pub fn goto_last_child(&mut self) -> bool { + return unsafe { ffi::ts_tree_cursor_goto_last_child(&mut self.0) }; + } + /// Move this cursor to the parent of its current node. /// /// This returns `true` if the cursor successfully moved, and returns `false` @@ -1333,6 +1388,15 @@ impl<'a> TreeCursor<'a> { }; } + /// Move this cursor to the previous sibling of its current node. + /// + /// This returns `true` if the cursor successfully moved, and returns + /// `false` if there was no previous sibling node. + #[doc(alias = "ts_tree_cursor_goto_previous_sibling")] + pub fn goto_previous_sibling(&mut self) -> bool { + return unsafe { ffi::ts_tree_cursor_goto_previous_sibling(&mut self.0) }; + } + /// Move this cursor to the first child of its current node that extends beyond /// the given byte offset. /// @@ -1370,6 +1434,15 @@ impl<'a> TreeCursor<'a> { pub fn reset(&mut self, node: Node<'a>) { unsafe { ffi::ts_tree_cursor_reset(&mut self.0, node.0) }; } + + /// Re-initialize a tree cursor to the same position as another cursor. + /// + /// Unlike `reset`, this will not lose parent information and + /// allows reusing already created cursors. + #[doc(alias = "ts_tree_cursor_reset_to")] + pub fn reset_to(&mut self, cursor: TreeCursor<'a>) { + unsafe { ffi::ts_tree_cursor_reset_to(&mut self.0, &cursor.0) }; + } } impl<'a> Clone for TreeCursor<'a> { @@ -1384,6 +1457,59 @@ impl<'a> Drop for TreeCursor<'a> { } } +impl<'a> LookaheadIterator<'a> { + /// Get the current language of the lookahead iterator. + #[doc(alias = "ts_lookahead_iterator_language")] + pub fn language(&self) -> Language { + Language(unsafe { ffi::ts_lookahead_iterator_language(self.0.as_ptr()) }) + } + + /// Get the current symbol of the lookahead iterator. + #[doc(alias = "ts_lookahead_iterator_current_symbol")] + pub fn current_symbol(&self) -> u16 { + unsafe { ffi::ts_lookahead_iterator_current_symbol(self.0.as_ptr()) } + } + + /// Reset the lookahead iterator. + /// + /// This returns `true` if the language was set successfully and `false` + /// otherwise. + #[doc(alias = "ts_lookahead_iterator_reset")] + pub fn reset(&self, language: &'a Language, state: u16) -> bool { + unsafe { ffi::ts_lookahead_iterator_reset(self.0.as_ptr(), language.0, state) } + } + + /// Reset the lookahead iterator to another state. + /// + /// This returns `true` if the iterator was reset to the given state and `false` + /// otherwise. + #[doc(alias = "ts_lookahead_iterator_reset_state")] + pub fn reset_state(&self, state: u16) -> bool { + unsafe { ffi::ts_lookahead_iterator_reset_state(self.0.as_ptr(), state) } + } +} + +impl<'a> Iterator for LookaheadIterator<'a> { + type Item = u16; + + #[doc(alias = "ts_lookahead_iterator_advance")] + fn next(&mut self) -> Option { + // the first symbol is always `0` so we can safely skip it + if !(unsafe { ffi::ts_lookahead_iterator_advance(self.0.as_ptr()) }) { + None + } else { + Some(self.current_symbol()) + } + } +} + +impl<'a> Drop for LookaheadIterator<'a> { + #[doc(alias = "ts_lookahead_iterator_delete")] + fn drop(&mut self) { + unsafe { ffi::ts_lookahead_iterator_delete(self.0.as_ptr()) } + } +} + impl Query { /// Create a new query from a string containing one or more S-expression /// patterns. diff --git a/lib/binding_web/binding.c b/lib/binding_web/binding.c index 27292911..4ba36f65 100644 --- a/lib/binding_web/binding.c +++ b/lib/binding_web/binding.c @@ -243,6 +243,13 @@ void ts_tree_cursor_reset_wasm(const TSTree *tree) { marshal_cursor(&cursor); } +void ts_tree_cursor_reset_to_wasm(const TSTree *_dst, const TSTree *_src) { + TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, _dst); + TSTreeCursor src = unmarshal_cursor(&TRANSFER_BUFFER[3], _src); + ts_tree_cursor_reset_to(&cursor, &src); + marshal_cursor(&cursor); +} + bool ts_tree_cursor_goto_first_child_wasm(const TSTree *tree) { TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, tree); bool result = ts_tree_cursor_goto_first_child(&cursor); @@ -250,6 +257,13 @@ bool ts_tree_cursor_goto_first_child_wasm(const TSTree *tree) { return result; } +bool ts_tree_cursor_goto_last_child_wasm(const TSTree *tree) { + TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, tree); + bool result = ts_tree_cursor_goto_last_child(&cursor); + marshal_cursor(&cursor); + return result; +} + bool ts_tree_cursor_goto_next_sibling_wasm(const TSTree *tree) { TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, tree); bool result = ts_tree_cursor_goto_next_sibling(&cursor); @@ -257,6 +271,13 @@ bool ts_tree_cursor_goto_next_sibling_wasm(const TSTree *tree) { return result; } +bool ts_tree_cursor_goto_previous_sibling_wasm(const TSTree *tree) { + TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, tree); + bool result = ts_tree_cursor_goto_previous_sibling(&cursor); + marshal_cursor(&cursor); + return result; +} + bool ts_tree_cursor_goto_parent_wasm(const TSTree *tree) { TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, tree); bool result = ts_tree_cursor_goto_parent(&cursor); @@ -270,6 +291,12 @@ uint16_t ts_tree_cursor_current_node_type_id_wasm(const TSTree *tree) { return ts_node_symbol(node); } +uint16_t ts_tree_cursor_current_node_state_id_wasm(const TSTree *tree) { + TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, tree); + TSNode node = ts_tree_cursor_current_node(&cursor); + return ts_node_parse_state(node); +} + bool ts_tree_cursor_current_node_is_named_wasm(const TSTree *tree) { TSTreeCursor cursor = unmarshal_cursor(TRANSFER_BUFFER, tree); TSNode node = ts_tree_cursor_current_node(&cursor); @@ -579,11 +606,21 @@ int ts_node_has_error_wasm(const TSTree *tree) { return ts_node_has_error(node); } +int ts_node_is_error_wasm(const TSTree *tree) { + TSNode node = unmarshal_node(tree); + return ts_node_is_error(node); +} + int ts_node_is_missing_wasm(const TSTree *tree) { TSNode node = unmarshal_node(tree); return ts_node_is_missing(node); } +uint16_t ts_node_parse_state_wasm(const TSTree *tree) { + TSNode node = unmarshal_node(tree); + return ts_node_parse_state(node); +} + /******************/ /* Section - Query */ /******************/ diff --git a/lib/binding_web/binding.js b/lib/binding_web/binding.js index 5352cb18..4203bd92 100644 --- a/lib/binding_web/binding.js +++ b/lib/binding_web/binding.js @@ -1,6 +1,7 @@ const C = Module; const INTERNAL = {}; const SIZE_OF_INT = 4; +const SIZE_OF_CURSOR = 3 * SIZE_OF_INT; const SIZE_OF_NODE = 5 * SIZE_OF_INT; const SIZE_OF_POINT = 2 * SIZE_OF_INT; const SIZE_OF_RANGE = 2 * SIZE_OF_INT + 2 * SIZE_OF_POINT; @@ -227,6 +228,11 @@ class Node { return getText(this.tree, this.startIndex, this.endIndex); } + get parseState() { + marshalNode(this); + return C._ts_node_parse_state_wasm(this.tree[0]); + } + isNamed() { marshalNode(this); return C._ts_node_is_named_wasm(this.tree[0]) === 1; @@ -242,6 +248,11 @@ class Node { return C._ts_node_has_changes_wasm(this.tree[0]) === 1; } + isError() { + marshalNode(this); + return C._ts_node_is_error_wasm(this.tree[0]) === 1; + } + isMissing() { marshalNode(this); return C._ts_node_is_missing_wasm(this.tree[0]) === 1; @@ -505,6 +516,13 @@ class TreeCursor { unmarshalTreeCursor(this); } + resetTo(cursor) { + marshalTreeCursor(this, TRANSFER_BUFFER); + marshalTreeCursor(cursor, TRANSFER_BUFFER + SIZE_OF_CURSOR); + C._ts_tree_cursor_reset_to_wasm(this.tree[0], cursor.tree[0]); + unmarshalTreeCursor(this); + } + get nodeType() { return this.tree.language.types[this.nodeTypeId] || 'ERROR'; } @@ -514,6 +532,11 @@ class TreeCursor { return C._ts_tree_cursor_current_node_type_id_wasm(this.tree[0]); } + get nodeStateId() { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_node_state_id_wasm(this.tree[0]); + } + get nodeId() { marshalTreeCursor(this); return C._ts_tree_cursor_current_node_id_wasm(this.tree[0]); @@ -580,6 +603,13 @@ class TreeCursor { return result === 1; } + gotoLastChild() { + marshalTreeCursor(this); + const result = C._ts_tree_cursor_goto_last_child_wasm(this.tree[0]); + unmarshalTreeCursor(this); + return result === 1; + } + gotoNextSibling() { marshalTreeCursor(this); const result = C._ts_tree_cursor_goto_next_sibling_wasm(this.tree[0]); @@ -587,6 +617,13 @@ class TreeCursor { return result === 1; } + gotoPreviousSibling() { + marshalTreeCursor(this); + const result = C._ts_tree_cursor_goto_previous_sibling_wasm(this.tree[0]); + unmarshalTreeCursor(this); + return result === 1; + } + gotoParent() { marshalTreeCursor(this); const result = C._ts_tree_cursor_goto_parent_wasm(this.tree[0]); @@ -624,6 +661,10 @@ class Language { return this.fields.length - 1; } + get stateCount() { + return C._ts_language_state_count(this[0]); + } + fieldIdForName(fieldName) { const result = this.fields.indexOf(fieldName); if (result !== -1) { @@ -663,6 +704,15 @@ class Language { return C._ts_language_type_is_visible_wasm(this[0], typeId) ? true : false; } + nextState(stateId, typeId) { + return C._ts_language_next_state(this[0], stateId, typeId); + } + + lookaheadIterator(stateId) { + const address = C._ts_lookahead_iterator_new(this[0], stateId); + if (address) return new LookaheadIterable(INTERNAL, address, this); + } + query(source) { const sourceLength = lengthBytesUTF8(source); const sourceAddress = C._malloc(sourceLength + 1); @@ -924,6 +974,53 @@ class Language { } } +class LookaheadIterable { + constructor(internal, address, language) { + assertInternal(internal); + this[0] = address; + this.language = language; + } + + get currentTypeId() { + return C._ts_lookahead_iterator_current_symbol(this[0]); + } + + get currentType() { + return this.language.types[this.currentTypeId] || 'ERROR' + } + + delete() { + C._ts_lookahead_iterator_delete(this[0]); + this[0] = 0; + } + + resetState(stateId) { + return C._ts_lookahead_iterator_reset_state(this[0], stateId); + } + + reset(language, stateId) { + if (C._ts_lookahead_iterator_reset(this[0], language, stateId)) { + this.language = language; + return true; + } + + return false; + } + + [Symbol.iterator]() { + const self = this; + return { + next() { + if (C._ts_lookahead_iterator_advance(self[0])) { + return { done: false, value: self.currentType }; + } + + return { done: true, value: "" }; + } + }; + } +} + class Query { constructor( internal, address, captureNames, textPredicates, predicates, diff --git a/lib/binding_web/exports.json b/lib/binding_web/exports.json index eb4553dc..ba34eedb 100644 --- a/lib/binding_web/exports.json +++ b/lib/binding_web/exports.json @@ -38,10 +38,12 @@ "_ts_language_type_is_named_wasm", "_ts_language_type_is_visible_wasm", "_ts_language_symbol_count", + "_ts_language_state_count", "_ts_language_symbol_for_name", "_ts_language_symbol_name", "_ts_language_symbol_type", "_ts_language_version", + "_ts_language_next_state", "_ts_node_child_by_field_id_wasm", "_ts_node_child_count_wasm", "_ts_node_child_wasm", @@ -53,8 +55,10 @@ "_ts_node_end_point_wasm", "_ts_node_has_changes_wasm", "_ts_node_has_error_wasm", + "_ts_node_is_error_wasm", "_ts_node_is_missing_wasm", "_ts_node_is_named_wasm", + "_ts_node_parse_state_wasm", "_ts_node_named_child_count_wasm", "_ts_node_named_child_wasm", "_ts_node_named_children_wasm", @@ -93,19 +97,29 @@ "_ts_tree_cursor_current_node_is_missing_wasm", "_ts_tree_cursor_current_node_is_named_wasm", "_ts_tree_cursor_current_node_type_id_wasm", + "_ts_tree_cursor_current_node_state_id_wasm", "_ts_tree_cursor_current_node_wasm", "_ts_tree_cursor_delete_wasm", "_ts_tree_cursor_end_index_wasm", "_ts_tree_cursor_end_position_wasm", "_ts_tree_cursor_goto_first_child_wasm", + "_ts_tree_cursor_goto_last_child", "_ts_tree_cursor_goto_next_sibling_wasm", + "_ts_tree_cursor_goto_previous_sibling_wasm", "_ts_tree_cursor_goto_parent_wasm", "_ts_tree_cursor_new_wasm", "_ts_tree_cursor_reset_wasm", + "_ts_tree_cursor_reset_to_wasm", "_ts_tree_cursor_start_index_wasm", "_ts_tree_cursor_start_position_wasm", "_ts_tree_delete", "_ts_tree_edit_wasm", "_ts_tree_get_changed_ranges_wasm", - "_ts_tree_root_node_wasm" + "_ts_tree_root_node_wasm", + "_ts_lookahead_iterator_new", + "_ts_lookahead_iterator_delete", + "_ts_lookahead_iterator_reset_state", + "_ts_lookahead_iterator_reset", + "_ts_lookahead_iterator_advance", + "_ts_lookahead_iterator_current_symbol" ] diff --git a/lib/binding_web/test/node-test.js b/lib/binding_web/test/node-test.js index 6bbcafb0..e897d991 100644 --- a/lib/binding_web/test/node-test.js +++ b/lib/binding_web/test/node-test.js @@ -268,6 +268,24 @@ describe("Node", () => { }); }); + describe(".isError()", () => { + it("returns true if the node is an error", () => { + tree = parser.parse("2 * * 3"); + const node = tree.rootNode; + assert.equal( + node.toString(), + '(program (expression_statement (binary_expression left: (number) (ERROR) right: (number))))' + ); + + const multi = node.firstChild.firstChild; + assert(multi.hasError()); + assert(!multi.children[0].isError()); + assert(!multi.children[1].isError()); + assert(multi.children[2].isError()); + assert(!multi.children[3].isError()); + }); + }); + describe(".isMissing()", () => { it("returns true if the node is missing from the source and was inserted via error recovery", () => { tree = parser.parse("(2 ||)"); @@ -308,6 +326,21 @@ describe("Node", () => { ); }); + describe(".parseState", () => { + const text = "10 * 5"; + + it(`returns node parse state ids`, async () => { + tree = await parser.parse(text) + const quotientNode = tree.rootNode.firstChild.firstChild; + const [numerator, slash, denominator] = quotientNode.children; + + assert.equal(tree.rootNode.parseState, 0); + assert.equal(numerator.parseState, 1); + assert.equal(slash.parseState, 553); + assert.equal(denominator.parseState, 185); + }) + }); + describe('.descendantsOfType(type, min, max)', () => { it('finds all of the descendants of the given type in the given range', () => { tree = parser.parse("a + 1 * b * 2 + c + 3"); diff --git a/lib/binding_web/tree-sitter-web.d.ts b/lib/binding_web/tree-sitter-web.d.ts index 016af4ae..709c2023 100644 --- a/lib/binding_web/tree-sitter-web.d.ts +++ b/lib/binding_web/tree-sitter-web.d.ts @@ -59,6 +59,7 @@ declare module 'web-tree-sitter' { tree: Tree; type: string; text: string; + parseState: number; startPosition: Point; endPosition: Point; startIndex: number; @@ -80,6 +81,7 @@ declare module 'web-tree-sitter' { hasChanges(): boolean; hasError(): boolean; equals(other: SyntaxNode): boolean; + isError(): boolean; isMissing(): boolean; isNamed(): boolean; toString(): string; @@ -104,6 +106,7 @@ declare module 'web-tree-sitter' { export interface TreeCursor { nodeType: string; nodeTypeId: number; + nodeStateId: number; nodeText: string; nodeId: number; nodeIsNamed: boolean; @@ -114,14 +117,17 @@ declare module 'web-tree-sitter' { endIndex: number; reset(node: SyntaxNode): void; + resetTo(cursor: TreeCursor): void; delete(): void; currentNode(): SyntaxNode; currentFieldId(): number; currentFieldName(): string; gotoParent(): boolean; gotoFirstChild(): boolean; + gotoLastChild(): boolean; gotoFirstChildForIndex(index: number): boolean; gotoNextSibling(): boolean; + gotoPreviousSibling(): boolean; } export interface Tree { @@ -141,6 +147,7 @@ declare module 'web-tree-sitter' { readonly version: number; readonly fieldCount: number; + readonly stateCount: number; readonly nodeTypeCount: number; fieldNameForId(fieldId: number): string | null; @@ -149,7 +156,20 @@ declare module 'web-tree-sitter' { nodeTypeForId(typeId: number): string | null; nodeTypeIsNamed(typeId: number): boolean; nodeTypeIsVisible(typeId: number): boolean; + nextState(stateId: number, typeId: number): number; query(source: string): Query; + lookaheadIterator(stateId: number): LookaheadIterable | null; + } + + class LookaheadIterable { + readonly language: Language; + readonly currentTypeId: number; + readonly currentType: string; + + delete(): void; + resetState(stateId: number): boolean; + reset(language: Language, stateId: number): boolean; + [Symbol.iterator](): Iterator; } interface QueryCapture { diff --git a/lib/include/tree_sitter/api.h b/lib/include/tree_sitter/api.h index 1e67f217..a9c8a182 100644 --- a/lib/include/tree_sitter/api.h +++ b/lib/include/tree_sitter/api.h @@ -32,6 +32,10 @@ extern "C" { /* Section - Types */ /*******************/ +#ifndef TREE_SITTER_PARSER_H_ +typedef uint16_t TSStateId; +#endif + typedef uint16_t TSSymbol; typedef uint16_t TSFieldId; typedef struct TSLanguage TSLanguage; @@ -39,6 +43,7 @@ typedef struct TSParser TSParser; typedef struct TSTree TSTree; typedef struct TSQuery TSQuery; typedef struct TSQueryCursor TSQueryCursor; +typedef struct TSLookaheadIterator TSLookaheadIterator; typedef enum { TSInputEncodingUTF8, @@ -504,6 +509,16 @@ bool ts_node_has_changes(TSNode); */ bool ts_node_has_error(TSNode); +/** + * Check if the node is a syntax error. +*/ +bool ts_node_is_error(TSNode); + +/** + * Get this node's parse state. +*/ +TSStateId ts_node_parse_state(TSNode); + /** * Get the node's immediate parent. */ @@ -637,6 +652,14 @@ void ts_tree_cursor_delete(TSTreeCursor *); */ void ts_tree_cursor_reset(TSTreeCursor *, TSNode); +/** + * Re-initialize a tree cursor to the same position as another cursor. + * + * Unlike `ts_tree_cursor_reset`, this will not lose parent information and + * allows reusing already created cursors. +*/ +void ts_tree_cursor_reset_to(TSTreeCursor *, const TSTreeCursor *); + /** * Get the tree cursor's current node. */ @@ -675,12 +698,21 @@ bool ts_tree_cursor_goto_parent(TSTreeCursor *); bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *); /** - * Move the cursor to the first child of its current node. + * Move the cursor to the previous sibling of its current node. + * + * This returns `true` if the cursor successfully moved, and returns `false` if + * there was no previous sibling node. + */ +bool ts_tree_cursor_goto_previous_sibling(TSTreeCursor *); + +/** + * Move the cursor to the first/last child of its current node. * * This returns `true` if the cursor successfully moved, and returns `false` * if there were no children. */ bool ts_tree_cursor_goto_first_child(TSTreeCursor *); +bool ts_tree_cursor_goto_last_child(TSTreeCursor *); /** * Move the cursor to the node that is the nth descendant of @@ -939,6 +971,11 @@ void ts_query_cursor_set_max_start_depth(TSQueryCursor *, uint32_t); */ uint32_t ts_language_symbol_count(const TSLanguage *); +/** + * Get the number of valid states in this language. +*/ +uint32_t ts_language_state_count(const TSLanguage *); + /** * Get a node type string for the given numerical id. */ @@ -986,6 +1023,71 @@ TSSymbolType ts_language_symbol_type(const TSLanguage *, TSSymbol); */ uint32_t ts_language_version(const TSLanguage *); +/** + * Get the next parse state. Combine this with lookahead iterators to generate + * completion suggestions or valid symbols in error nodes. +*/ +TSStateId ts_language_next_state(const TSLanguage *, TSStateId, TSSymbol); + +/********************************/ +/* Section - Lookahead Iterator */ +/********************************/ + +/** + * Create a new lookahead iterator for the given language and parse state. + * + * This returns `NULL` if state is invalid for the language. + * + * Repeatedly using `ts_lookahead_iterator_advance` and + * `ts_lookahead_iterator_current_symbol` will generate valid symbols in the + * given parse state. Newly created lookahead iterators will contain the `ERROR` + * symbol. + * + * Lookahead iterators can be useful to generate suggestions and improve syntax + * error diagnostics. To get symbols valid in an ERROR node, use the lookahead + * iterator on its first leaf node state. For `MISSING` nodes, a lookahead + * iterator created on the previous non-extra leaf node may be appropriate. +*/ +TSLookaheadIterator *ts_lookahead_iterator_new(const TSLanguage *, TSStateId); + +/** + * Delete a lookahead iterator freeing all the memory used. +*/ +void ts_lookahead_iterator_delete(TSLookaheadIterator *); + +/** + * Reset the lookahead iterator to another state. + * + * This returns `true` if the iterator was reset to the given state and `false` + * otherwise. +*/ +bool ts_lookahead_iterator_reset_state(TSLookaheadIterator *, TSStateId); + +/** + * Reset the lookahead iterator. + * + * This returns `true` if the language was set successfully and `false` + * otherwise. +*/ +bool ts_lookahead_iterator_reset(TSLookaheadIterator *, const TSLanguage *, TSStateId); + +/** + * Get the current language of the lookahead iterator. +*/ +const TSLanguage * ts_lookahead_iterator_language(const TSLookaheadIterator *); + +/** + * Advance the lookahead iterator to the next symbol. + * + * This returns `true` if there is a new symbol and `false` otherwise. +*/ +bool ts_lookahead_iterator_advance(TSLookaheadIterator *); + +/** + * Get the current symbol of the lookahead iterator; +*/ +TSSymbol ts_lookahead_iterator_current_symbol(const TSLookaheadIterator *); + /**********************************/ /* Section - Global Configuration */ /**********************************/ diff --git a/lib/include/tree_sitter/parser.h b/lib/include/tree_sitter/parser.h index ac773580..c7ad1907 100644 --- a/lib/include/tree_sitter/parser.h +++ b/lib/include/tree_sitter/parser.h @@ -13,7 +13,9 @@ extern "C" { #define ts_builtin_sym_end 0 #define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024 +#ifndef TREE_SITTER_API_H_ typedef uint16_t TSStateId; +#endif #ifndef TREE_SITTER_API_H_ typedef uint16_t TSSymbol; diff --git a/lib/src/language.c b/lib/src/language.c index c87a8dc1..d8d07c2c 100644 --- a/lib/src/language.c +++ b/lib/src/language.c @@ -7,6 +7,10 @@ uint32_t ts_language_symbol_count(const TSLanguage *self) { return self->symbol_count + self->alias_count; } +uint32_t ts_language_state_count(const TSLanguage *self) { + return self->state_count; +} + uint32_t ts_language_version(const TSLanguage *self) { return self->version; } @@ -56,6 +60,28 @@ TSSymbol ts_language_public_symbol( return self->public_symbol_map[symbol]; } +TSStateId ts_language_next_state( + const TSLanguage *self, + TSStateId state, + TSSymbol symbol +) { + if (symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat) { + return 0; + } else if (symbol < self->token_count) { + uint32_t count; + const TSParseAction *actions = ts_language_actions(self, state, symbol, &count); + if (count > 0) { + TSParseAction action = actions[count - 1]; + if (action.type == TSParseActionTypeShift) { + return action.shift.extra ? state : action.shift.state; + } + } + return 0; + } else { + return ts_language_lookup(self, state, symbol); + } +} + const char *ts_language_symbol_name( const TSLanguage *self, TSSymbol symbol @@ -135,3 +161,43 @@ TSFieldId ts_language_field_id_for_name( } return 0; } + +TSLookaheadIterator *ts_lookahead_iterator_new(const TSLanguage *self, TSStateId state) { + if (state >= self->state_count) return NULL; + LookaheadIterator *iterator = ts_malloc(sizeof(LookaheadIterator)); + *iterator = ts_language_lookaheads(self, state); + return (TSLookaheadIterator *)iterator; +} + +void ts_lookahead_iterator_delete(TSLookaheadIterator *self) { + ts_free(self); +} + +bool ts_lookahead_iterator_reset_state(TSLookaheadIterator * self, TSStateId state) { + LookaheadIterator *iterator = (LookaheadIterator *)self; + if (state >= iterator->language->state_count) return false; + *iterator = ts_language_lookaheads(iterator->language, state); + return true; +} + +const TSLanguage * ts_lookahead_iterator_language(const TSLookaheadIterator *self) { + LookaheadIterator *iterator = (LookaheadIterator *)self; + return iterator->language; +} + +bool ts_lookahead_iterator_reset(TSLookaheadIterator *self, const TSLanguage *language, TSStateId state) { + if (state >= language->state_count) return false; + LookaheadIterator *iterator = (LookaheadIterator *)self; + *iterator = ts_language_lookaheads(language, state); + return true; +} + +bool ts_lookahead_iterator_advance(TSLookaheadIterator *self) { + LookaheadIterator *iterator = (LookaheadIterator *)self; + return ts_lookahead_iterator_next(iterator); +} + +TSSymbol ts_lookahead_iterator_current_symbol(const TSLookaheadIterator *self) { + LookaheadIterator *iterator = (LookaheadIterator *)self; + return iterator->symbol; +} diff --git a/lib/src/language.h b/lib/src/language.h index db61b602..002f564f 100644 --- a/lib/src/language.h +++ b/lib/src/language.h @@ -38,6 +38,8 @@ TSSymbolMetadata ts_language_symbol_metadata(const TSLanguage *, TSSymbol); TSSymbol ts_language_public_symbol(const TSLanguage *, TSSymbol); +TSStateId ts_language_next_state(const TSLanguage *self, TSStateId state, TSSymbol symbol); + static inline bool ts_language_is_symbol_external(const TSLanguage *self, TSSymbol symbol) { return 0 < symbol && symbol < self->external_token_count + 1; } @@ -178,28 +180,6 @@ static inline bool ts_lookahead_iterator_next(LookaheadIterator *self) { return true; } -static inline TSStateId ts_language_next_state( - const TSLanguage *self, - TSStateId state, - TSSymbol symbol -) { - if (symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat) { - return 0; - } else if (symbol < self->token_count) { - uint32_t count; - const TSParseAction *actions = ts_language_actions(self, state, symbol, &count); - if (count > 0) { - TSParseAction action = actions[count - 1]; - if (action.type == TSParseActionTypeShift) { - return action.shift.extra ? state : action.shift.state; - } - } - return 0; - } else { - return ts_language_lookup(self, state, symbol); - } -} - // Whether the state is a "primary state". If this returns false, it indicates that there exists // another state that behaves identically to this one with respect to query analysis. static inline bool ts_language_state_is_primary( diff --git a/lib/src/node.c b/lib/src/node.c index eafaa7bb..8ef2d150 100644 --- a/lib/src/node.c +++ b/lib/src/node.c @@ -468,6 +468,15 @@ uint32_t ts_node_descendant_count(TSNode self) { return ts_subtree_visible_descendant_count(ts_node__subtree(self)) + 1; } +bool ts_node_is_error(TSNode self) { + TSSymbol symbol = ts_node_symbol(self); + return symbol == ts_builtin_sym_error; +} + +TSStateId ts_node_parse_state(TSNode self) { + return ts_subtree_parse_state(ts_node__subtree(self)); +} + TSNode ts_node_parent(TSNode self) { TSNode node = ts_tree_root_node(self.tree); uint32_t end_byte = ts_node_end_byte(self); diff --git a/lib/src/tree_cursor.c b/lib/src/tree_cursor.c index 97a53152..624cfa2f 100644 --- a/lib/src/tree_cursor.c +++ b/lib/src/tree_cursor.c @@ -97,6 +97,40 @@ static inline bool ts_tree_cursor_child_iterator_next( return true; } +static inline bool ts_tree_cursor_child_iterator_previous( + CursorChildIterator *self, + TreeCursorEntry *result, + bool *visible +) { + // this is mostly a reverse `ts_tree_cursor_child_iterator_next` taking into + // account unsigned underflow + if (!self->parent.ptr || (int8_t)self->child_index == -1) return false; + const Subtree *child = &ts_subtree_children(self->parent)[self->child_index]; + *result = (TreeCursorEntry) { + .subtree = child, + .position = self->position, + .child_index = self->child_index, + .structural_child_index = self->structural_child_index, + }; + *visible = ts_subtree_visible(*child); + bool extra = ts_subtree_extra(*child); + if (!extra && self->alias_sequence) { + *visible |= self->alias_sequence[self->structural_child_index]; + self->structural_child_index--; + } + + self->position = length_sub(self->position, ts_subtree_padding(*child)); + self->child_index--; + + // unsigned can underflow so compare it to child_count + if (self->child_index < self->parent.ptr->child_count) { + Subtree previous_child = ts_subtree_children(self->parent)[self->child_index]; + self->position = length_sub(self->position, ts_subtree_size(previous_child)); + } + + return true; +} + // TSTreeCursor - lifecycle TSTreeCursor ts_tree_cursor_new(TSNode node) { @@ -163,6 +197,47 @@ bool ts_tree_cursor_goto_first_child(TSTreeCursor *self) { return false; } +TreeCursorStep ts_tree_cursor_goto_last_child_internal(TSTreeCursor *_self) { + TreeCursor *self = (TreeCursor *)_self; + bool visible; + TreeCursorEntry entry; + CursorChildIterator iterator = ts_tree_cursor_iterate_children(self); + if (!iterator.parent.ptr || iterator.parent.ptr->child_count == 0) return TreeCursorStepNone; + + TreeCursorEntry last_entry; + bool last_visible; + while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) { + if (visible) { + last_entry = entry; + last_visible = true; + } + else if (ts_subtree_visible_child_count(*entry.subtree) > 0) { + last_entry = entry; + last_visible = false; + } + } + if (last_entry.subtree) { + array_push(&self->stack, last_entry); + return last_visible? TreeCursorStepVisible : TreeCursorStepHidden; + } + + return TreeCursorStepNone; +} + +bool ts_tree_cursor_goto_last_child(TSTreeCursor *self) { + for (;;) { + switch (ts_tree_cursor_goto_last_child_internal(self)) { + case TreeCursorStepHidden: + continue; + case TreeCursorStepVisible: + return true; + default: + return false; + } + } + return false; +} + static inline int64_t ts_tree_cursor_goto_first_child_for_byte_and_point( TSTreeCursor *_self, uint32_t goal_byte, @@ -213,7 +288,9 @@ int64_t ts_tree_cursor_goto_first_child_for_point(TSTreeCursor *self, TSPoint go return ts_tree_cursor_goto_first_child_for_byte_and_point(self, 0, goal_point); } -TreeCursorStep ts_tree_cursor_goto_next_sibling_internal(TSTreeCursor *_self) { +TreeCursorStep ts_tree_cursor_goto_sibling_internal( + TSTreeCursor *_self, + bool (*advance)(CursorChildIterator *, TreeCursorEntry *, bool *)) { TreeCursor *self = (TreeCursor *)_self; uint32_t initial_size = self->stack.size; @@ -226,10 +303,10 @@ TreeCursorStep ts_tree_cursor_goto_next_sibling_internal(TSTreeCursor *_self) { iterator.descendant_index = entry.descendant_index; bool visible = false; - ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible); + advance(&iterator, &entry, &visible); if (visible && self->stack.size + 1 < initial_size) break; - while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) { + while (advance(&iterator, &entry, &visible)) { if (visible) { array_push(&self->stack, entry); return TreeCursorStepVisible; @@ -246,6 +323,10 @@ TreeCursorStep ts_tree_cursor_goto_next_sibling_internal(TSTreeCursor *_self) { return TreeCursorStepNone; } +TreeCursorStep ts_tree_cursor_goto_next_sibling_internal(TSTreeCursor *_self) { + return ts_tree_cursor_goto_sibling_internal(_self, ts_tree_cursor_child_iterator_next); +} + bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *self) { switch (ts_tree_cursor_goto_next_sibling_internal(self)) { case TreeCursorStepHidden: @@ -258,6 +339,50 @@ bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *self) { } } +TreeCursorStep ts_tree_cursor_goto_previous_sibling_internal(TSTreeCursor *_self) { + // since subtracting across row loses column information, we may have to + // restore it + TreeCursor *self = (TreeCursor *)_self; + + // for that, save current position before traversing + Length position = array_back(&self->stack)->position; + TreeCursorStep step = ts_tree_cursor_goto_sibling_internal( + _self, ts_tree_cursor_child_iterator_previous); + if (step == TreeCursorStepNone) + return step; + + // if row has not changed, column is still valid + if (array_back(&self->stack)->position.extent.row == position.extent.row) + return step; + + // restore position from the parent node + const TreeCursorEntry *parent = &self->stack.contents[self->stack.size - 2]; + position = parent->position; + uint32_t child_index = array_back(&self->stack)->child_index; + const Subtree *children = ts_subtree_children((*(parent->subtree))); + for (uint32_t i = 0; i < child_index; ++i) { + position = length_add(position, ts_subtree_total_size(children[i])); + } + if (child_index > 0) + position = length_add(position, ts_subtree_padding(children[child_index])); + + array_back(&self->stack)->position = position; + + return step; +} + +bool ts_tree_cursor_goto_previous_sibling(TSTreeCursor *self) { + switch (ts_tree_cursor_goto_previous_sibling_internal(self)) { + case TreeCursorStepHidden: + ts_tree_cursor_goto_last_child(self); + return true; + case TreeCursorStepVisible: + return true; + default: + return false; + } +} + bool ts_tree_cursor_goto_parent(TSTreeCursor *_self) { TreeCursor *self = (TreeCursor *)_self; for (unsigned i = self->stack.size - 2; i + 1 > 0; i--) { @@ -556,3 +681,11 @@ TSTreeCursor ts_tree_cursor_copy(const TSTreeCursor *_cursor) { array_push_all(©->stack, &cursor->stack); return res; } + +void ts_tree_cursor_reset_to(TSTreeCursor *_dst, const TSTreeCursor *_src) { + const TreeCursor *cursor = (const TreeCursor *)_src; + TreeCursor *copy = (TreeCursor *)_dst; + copy->tree = cursor->tree; + array_clear(©->stack); + array_push_all(©->stack, &cursor->stack); +}