From 666dfb76d21166e315ddbf34049980e5081832b9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 9 May 2018 16:38:56 -0700 Subject: [PATCH] Remove document parameter from ts_node_type, ts_node_string Co-Authored-By: Rick Winfrey --- include/tree_sitter/runtime.h | 22 +++-- project.gyp | 1 + src/runtime/document.c | 15 ++-- src/runtime/document.h | 6 +- src/runtime/get_changed_ranges.c | 55 ++++++------- src/runtime/get_changed_ranges.h | 11 +-- src/runtime/tree_cursor.c | 134 +++++++++++++++++++++++++++++++ src/runtime/tree_cursor.h | 20 +++++ test/runtime/node_test.cc | 128 +++++++++++++++++++++++++++++ 9 files changed, 339 insertions(+), 53 deletions(-) create mode 100644 src/runtime/tree_cursor.c create mode 100644 src/runtime/tree_cursor.h diff --git a/include/tree_sitter/runtime.h b/include/tree_sitter/runtime.h index 934d5320..d3e282fb 100644 --- a/include/tree_sitter/runtime.h +++ b/include/tree_sitter/runtime.h @@ -14,6 +14,7 @@ extern "C" { typedef unsigned short TSSymbol; typedef struct TSLanguage TSLanguage; typedef struct TSDocument TSDocument; +typedef struct TSTreeCursor TSTreeCursor; typedef enum { TSInputEncodingUTF8, @@ -70,6 +71,12 @@ typedef struct { TSSymbol alias_symbol; } TSNode; +typedef struct { + TSRange **changed_ranges; + uint32_t *changed_range_count; + bool halt_on_error; +} TSParseOptions; + uint32_t ts_node_start_byte(TSNode); TSPoint ts_node_start_point(TSNode); uint32_t ts_node_end_byte(TSNode); @@ -112,19 +119,18 @@ void ts_document_print_debugging_graphs(TSDocument *, bool); void ts_document_edit(TSDocument *, TSInputEdit); void ts_document_parse(TSDocument *); void ts_document_parse_and_get_changed_ranges(TSDocument *, TSRange **, uint32_t *); - -typedef struct { - TSRange **changed_ranges; - uint32_t *changed_range_count; - bool halt_on_error; -} TSParseOptions; - void ts_document_parse_with_options(TSDocument *, TSParseOptions); - void ts_document_invalidate(TSDocument *); TSNode ts_document_root_node(const TSDocument *); +TSTreeCursor *ts_document_tree_cursor(const TSDocument *); uint32_t ts_document_parse_count(const TSDocument *); +void ts_tree_cursor_delete(TSTreeCursor *); +bool ts_tree_cursor_goto_first_child(TSTreeCursor *); +bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *); +bool ts_tree_cursor_goto_parent(TSTreeCursor *); +TSNode ts_tree_cursor_current_node(TSTreeCursor *); + uint32_t ts_language_symbol_count(const TSLanguage *); const char *ts_language_symbol_name(const TSLanguage *, TSSymbol); TSSymbolType ts_language_symbol_type(const TSLanguage *, TSSymbol); diff --git a/project.gyp b/project.gyp index b968a171..f59c3c91 100644 --- a/project.gyp +++ b/project.gyp @@ -96,6 +96,7 @@ 'src/runtime/parser.c', 'src/runtime/string_input.c', 'src/runtime/tree.c', + 'src/runtime/tree_cursor.c', 'src/runtime/utf16.c', 'externals/utf8proc/utf8proc.c', ], diff --git a/src/runtime/document.c b/src/runtime/document.c index ddd89b43..ab10ae49 100644 --- a/src/runtime/document.c +++ b/src/runtime/document.c @@ -3,6 +3,7 @@ #include "runtime/parser.h" #include "runtime/string_input.h" #include "runtime/document.h" +#include "runtime/tree_cursor.h" #include "runtime/get_changed_ranges.h" #define LOG(...) \ @@ -12,15 +13,15 @@ TSDocument *ts_document_new() { TSDocument *self = ts_calloc(1, sizeof(TSDocument)); parser_init(&self->parser); - array_init(&self->tree_path1); - array_init(&self->tree_path2); + array_init(&self->cursor1.stack); + array_init(&self->cursor2.stack); return self; } void ts_document_free(TSDocument *self) { if (self->tree) ts_tree_release(&self->parser.tree_pool, self->tree); - if (self->tree_path1.contents) array_delete(&self->tree_path1); - if (self->tree_path2.contents) array_delete(&self->tree_path2); + if (self->cursor1.stack.contents) array_delete(&self->cursor1.stack); + if (self->cursor2.stack.contents) array_delete(&self->cursor2.stack); parser_destroy(&self->parser); ts_document_set_input(self, (TSInput){ NULL, @@ -141,7 +142,7 @@ void ts_document_parse_with_options(TSDocument *self, TSParseOptions options) { if (options.changed_ranges && options.changed_range_count) { *options.changed_range_count = ts_tree_get_changed_ranges( - old_tree, tree, &self->tree_path1, &self->tree_path2, + old_tree, tree, &self->cursor1, &self->cursor2, self->parser.language, options.changed_ranges ); @@ -181,3 +182,7 @@ TSNode ts_document_root_node(const TSDocument *self) { uint32_t ts_document_parse_count(const TSDocument *self) { return self->parse_count; } + +TSTreeCursor *ts_document_tree_cursor(const TSDocument *self) { + return ts_tree_cursor_new(self); +} diff --git a/src/runtime/document.h b/src/runtime/document.h index 0be05f14..986fcec9 100644 --- a/src/runtime/document.h +++ b/src/runtime/document.h @@ -3,15 +3,15 @@ #include "runtime/parser.h" #include "runtime/tree.h" -#include "runtime/get_changed_ranges.h" +#include "runtime/tree_cursor.h" #include struct TSDocument { Parser parser; TSInput input; Tree *tree; - TreePath tree_path1; - TreePath tree_path2; + TSTreeCursor cursor1; + TSTreeCursor cursor2; size_t parse_count; bool valid; bool owns_input; diff --git a/src/runtime/get_changed_ranges.c b/src/runtime/get_changed_ranges.c index 26211613..7632af24 100644 --- a/src/runtime/get_changed_ranges.c +++ b/src/runtime/get_changed_ranges.c @@ -2,6 +2,7 @@ #include "runtime/tree.h" #include "runtime/language.h" #include "runtime/error_costs.h" +#include "runtime/tree_cursor.h" #include // #define DEBUG_GET_CHANGED_RANGES @@ -24,22 +25,22 @@ static void range_array_add(RangeArray *results, TSPoint start, TSPoint end) { } typedef struct { - TreePath path; + TSTreeCursor cursor; const TSLanguage *language; unsigned visible_depth; bool in_padding; } Iterator; -static Iterator iterator_new(TreePath *path, Tree *tree, const TSLanguage *language) { - array_clear(path); - array_push(path, ((TreePathEntry){ +static Iterator iterator_new(TSTreeCursor *cursor, Tree *tree, const TSLanguage *language) { + array_clear(&cursor->stack); + array_push(&cursor->stack, ((TreeCursorEntry){ .tree = tree, .position = length_zero(), .child_index = 0, .structural_child_index = 0, })); return (Iterator) { - .path = *path, + .cursor = *cursor, .language = language, .visible_depth = 1, .in_padding = false, @@ -47,11 +48,11 @@ static Iterator iterator_new(TreePath *path, Tree *tree, const TSLanguage *langu } static bool iterator_done(Iterator *self) { - return self->path.size == 0; + return self->cursor.stack.size == 0; } Length iterator_start_position(Iterator *self) { - TreePathEntry entry = *array_back(&self->path); + TreeCursorEntry entry = *array_back(&self->cursor.stack); if (self->in_padding) { return entry.position; } else { @@ -60,7 +61,7 @@ Length iterator_start_position(Iterator *self) { } Length iterator_end_position(Iterator *self) { - TreePathEntry entry = *array_back(&self->path); + TreeCursorEntry entry = *array_back(&self->cursor.stack); Length result = length_add(entry.position, entry.tree->padding); if (self->in_padding) { return result; @@ -70,10 +71,10 @@ Length iterator_end_position(Iterator *self) { } static bool iterator_tree_is_visible(const Iterator *self) { - TreePathEntry entry = *array_back(&self->path); + TreeCursorEntry entry = *array_back(&self->cursor.stack); if (entry.tree->visible) return true; - if (self->path.size > 1) { - Tree *parent = self->path.contents[self->path.size - 2].tree; + if (self->cursor.stack.size > 1) { + Tree *parent = self->cursor.stack.contents[self->cursor.stack.size - 2].tree; const TSSymbol *alias_sequence = ts_language_alias_sequence(self->language, parent->alias_sequence_id); return alias_sequence && alias_sequence[entry.structural_child_index] != 0; } @@ -82,7 +83,7 @@ static bool iterator_tree_is_visible(const Iterator *self) { static void iterator_get_visible_state(const Iterator *self, Tree **tree, TSSymbol *alias_symbol, uint32_t *start_byte) { - uint32_t i = self->path.size - 1; + uint32_t i = self->cursor.stack.size - 1; if (self->in_padding) { if (i == 0) return; @@ -90,10 +91,10 @@ static void iterator_get_visible_state(const Iterator *self, Tree **tree, } for (; i + 1 > 0; i--) { - TreePathEntry entry = self->path.contents[i]; + TreeCursorEntry entry = self->cursor.stack.contents[i]; if (i > 0) { - Tree *parent = self->path.contents[i - 1].tree; + Tree *parent = self->cursor.stack.contents[i - 1].tree; const TSSymbol *alias_sequence = ts_language_alias_sequence( self->language, parent->alias_sequence_id @@ -114,8 +115,8 @@ static void iterator_get_visible_state(const Iterator *self, Tree **tree, static void iterator_ascend(Iterator *self) { if (iterator_done(self)) return; if (iterator_tree_is_visible(self) && !self->in_padding) self->visible_depth--; - if (array_back(&self->path)->child_index > 0) self->in_padding = false; - self->path.size--; + if (array_back(&self->cursor.stack)->child_index > 0) self->in_padding = false; + self->cursor.stack.size--; } static bool iterator_descend(Iterator *self, uint32_t goal_position) { @@ -124,7 +125,7 @@ static bool iterator_descend(Iterator *self, uint32_t goal_position) { bool did_descend; do { did_descend = false; - TreePathEntry entry = *array_back(&self->path); + TreeCursorEntry entry = *array_back(&self->cursor.stack); Length position = entry.position; uint32_t structural_child_index = 0; for (uint32_t i = 0; i < entry.tree->children.size; i++) { @@ -133,7 +134,7 @@ static bool iterator_descend(Iterator *self, uint32_t goal_position) { Length child_right = length_add(child_left, child->size); if (child_right.bytes > goal_position) { - array_push(&self->path, ((TreePathEntry){ + array_push(&self->cursor.stack, ((TreeCursorEntry){ .tree = child, .position = position, .child_index = i, @@ -174,10 +175,10 @@ static void iterator_advance(Iterator *self) { for (;;) { if (iterator_tree_is_visible(self)) self->visible_depth--; - TreePathEntry entry = array_pop(&self->path); + TreeCursorEntry entry = array_pop(&self->cursor.stack); if (iterator_done(self)) return; - Tree *parent = array_back(&self->path)->tree; + Tree *parent = array_back(&self->cursor.stack)->tree; uint32_t child_index = entry.child_index + 1; if (parent->children.size > child_index) { Length position = length_add(entry.position, ts_tree_total_size(entry.tree)); @@ -185,7 +186,7 @@ static void iterator_advance(Iterator *self) { if (!entry.tree->extra) structural_child_index++; Tree *next_child = parent->children.contents[child_index]; - array_push(&self->path, ((TreePathEntry){ + array_push(&self->cursor.stack, ((TreeCursorEntry){ .tree = next_child, .position = position, .child_index = child_index, @@ -246,7 +247,7 @@ IteratorComparison iterator_compare(const Iterator *old_iter, const Iterator *ne #ifdef DEBUG_GET_CHANGED_RANGES static inline void iterator_print_state(Iterator *self) { - TreePathEntry entry = *array_back(&self->path); + TreeCursorEntry entry = *array_back(&self->cursor.stack); TSPoint start = iterator_start_position(self).extent; TSPoint end = iterator_end_position(self).extent; const char *name = ts_language_symbol_name(self->language, entry.tree->symbol); @@ -261,12 +262,12 @@ static inline void iterator_print_state(Iterator *self) { #endif unsigned ts_tree_get_changed_ranges(Tree *old_tree, Tree *new_tree, - TreePath *path1, TreePath *path2, + TSTreeCursor *cursor1, TSTreeCursor *cursor2, const TSLanguage *language, TSRange **ranges) { RangeArray results = array_new(); - Iterator old_iter = iterator_new(path1, old_tree, language); - Iterator new_iter = iterator_new(path2, new_tree, language); + Iterator old_iter = iterator_new(cursor1, old_tree, language); + Iterator new_iter = iterator_new(cursor2, new_tree, language); Length position = iterator_start_position(&old_iter); Length next_position = iterator_start_position(&new_iter); @@ -348,8 +349,8 @@ unsigned ts_tree_get_changed_ranges(Tree *old_tree, Tree *new_tree, position = next_position; } while (!iterator_done(&old_iter) && !iterator_done(&new_iter)); - *path1 = old_iter.path; - *path2 = new_iter.path; + *cursor1 = old_iter.cursor; + *cursor2 = new_iter.cursor; *ranges = results.contents; return results.size; } diff --git a/src/runtime/get_changed_ranges.h b/src/runtime/get_changed_ranges.h index 360cdbd4..fa408aaa 100644 --- a/src/runtime/get_changed_ranges.h +++ b/src/runtime/get_changed_ranges.h @@ -3,17 +3,8 @@ #include "runtime/tree.h" -typedef struct { - Tree *tree; - Length position; - uint32_t child_index; - uint32_t structural_child_index; -} TreePathEntry; - -typedef Array(TreePathEntry) TreePath; - unsigned ts_tree_get_changed_ranges( - Tree *old_tree, Tree *new_tree, TreePath *path1, TreePath *path2, + Tree *old_tree, Tree *new_tree, TSTreeCursor *cursor1, TSTreeCursor *cursor2, const TSLanguage *language, TSRange **ranges ); diff --git a/src/runtime/tree_cursor.c b/src/runtime/tree_cursor.c new file mode 100644 index 00000000..db8298d2 --- /dev/null +++ b/src/runtime/tree_cursor.c @@ -0,0 +1,134 @@ +#include "tree_sitter/runtime.h" +#include "runtime/alloc.h" +#include "runtime/tree_cursor.h" +#include "runtime/document.h" +#include "runtime/language.h" + +TSTreeCursor *ts_tree_cursor_new(const TSDocument *document) { + TSTreeCursor *self = ts_malloc(sizeof(TSTreeCursor)); + self->document = document; + array_init(&self->stack); + array_push(&self->stack, ((TreeCursorEntry) { + .tree = document->tree, + .position = length_zero(), + .child_index = 0, + .structural_child_index = 0, + })); + return self; +} + +void ts_tree_cursor_delete(TSTreeCursor *self) { + array_delete(&self->stack); + ts_free(self); +} + +bool ts_tree_cursor_goto_first_child(TSTreeCursor *self) { + TreeCursorEntry *last_entry = array_back(&self->stack); + Tree *tree = last_entry->tree; + Length position = last_entry->position; + + bool did_descend; + do { + did_descend = false; + + uint32_t structural_child_index = 0; + for (uint32_t i = 0; i < tree->children.size; i++) { + Tree *child = tree->children.contents[i]; + if (child->visible || child->visible_child_count > 0) { + array_push(&self->stack, ((TreeCursorEntry) { + .tree = child, + .child_index = i, + .structural_child_index = structural_child_index, + .position = position, + })); + + if (child->visible) { + return true; + } else { + tree = child; + did_descend = true; + break; + } + } + if (!child->extra) structural_child_index++; + position = length_add(position, ts_tree_total_size(child)); + } + } while (did_descend); + + return false; +} + +bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *self) { + TreeCursorEntry *child_entry = array_back(&self->stack); + + for (unsigned i = self->stack.size - 2; i + 1 > 0; i--) { + TreeCursorEntry *parent_entry = &self->stack.contents[i]; + + Tree *parent = parent_entry->tree; + uint32_t child_index = child_entry->child_index; + uint32_t structural_child_index = child_entry->structural_child_index; + Length position = child_entry->position; + Tree *child = parent->children.contents[child_index]; + + while (++child_index < parent->children.size) { + if (!child->extra) structural_child_index++; + position = length_add(position, ts_tree_total_size(child)); + child = parent->children.contents[child_index]; + + if (child->visible || child->visible_child_count > 0) { + self->stack.contents[i + 1] = (TreeCursorEntry) { + .tree = child, + .child_index = child_index, + .structural_child_index = structural_child_index, + .position = position, + }; + self->stack.size = i + 2; + + if (child->visible) { + return true; + } else { + ts_tree_cursor_goto_first_child(self); + return true; + } + } + } + + child_entry = parent_entry; + if (parent->visible) break; + } + + return false; +} + +bool ts_tree_cursor_goto_parent(TSTreeCursor *self) { + for (unsigned i = self->stack.size - 2; i + 1 > 0; i--) { + TreeCursorEntry *entry = &self->stack.contents[i]; + if (entry->tree->visible) { + self->stack.size = i + 1; + return true; + } + } + return false; +} + +TSNode ts_tree_cursor_current_node(TSTreeCursor *self) { + TreeCursorEntry *last_entry = array_back(&self->stack); + TSSymbol alias_symbol = 0; + if (self->stack.size > 1) { + TreeCursorEntry *parent_entry = &self->stack.contents[self->stack.size - 2]; + const TSSymbol *alias_sequence = ts_language_alias_sequence( + self->document->parser.language, + parent_entry->tree->alias_sequence_id + ); + if (alias_sequence) { + alias_symbol = alias_sequence[last_entry->structural_child_index]; + } + } + return (TSNode) { + .document = self->document, + .subtree = last_entry->tree, + .position = last_entry->position.extent, + .byte = last_entry->position.bytes, + .alias_symbol = alias_symbol, + }; +} diff --git a/src/runtime/tree_cursor.h b/src/runtime/tree_cursor.h new file mode 100644 index 00000000..33e1d802 --- /dev/null +++ b/src/runtime/tree_cursor.h @@ -0,0 +1,20 @@ +#ifndef RUNTIME_TREE_CURSOR_H_ +#define RUNTIME_TREE_CURSOR_H_ + +#include "runtime/tree.h" + +typedef struct { + Tree *tree; + Length position; + uint32_t child_index; + uint32_t structural_child_index; +} TreeCursorEntry; + +struct TSTreeCursor { + const TSDocument *document; + Array(TreeCursorEntry) stack; +}; + +TSTreeCursor *ts_tree_cursor_new(const TSDocument *); + +#endif // RUNTIME_TREE_CURSOR_H_ diff --git a/test/runtime/node_test.cc b/test/runtime/node_test.cc index 91717ed2..808084be 100644 --- a/test/runtime/node_test.cc +++ b/test/runtime/node_test.cc @@ -517,4 +517,132 @@ describe("Node", [&]() { }); }); +describe("TreeCursor", [&]() { + TSDocument *document; + TSTreeCursor *cursor; + + before_each([&]() { + record_alloc::start(); + + document = ts_document_new(); + ts_document_set_language(document, load_real_language("json")); + ts_document_set_input_string(document, json_string.c_str()); + ts_document_parse(document); + + cursor = ts_document_tree_cursor(document); + }); + + after_each([&]() { + ts_tree_cursor_delete(cursor); + ts_document_free(document); + + record_alloc::stop(); + AssertThat(record_alloc::outstanding_allocation_indices(), IsEmpty()); + }); + + it("can walk the tree", [&]() { + TSNode node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("value")); + AssertThat(ts_node_start_byte(node), Equals(array_index)); + + AssertThat(ts_tree_cursor_goto_first_child(cursor), IsTrue()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("array")); + AssertThat(ts_node_start_byte(node), Equals(array_index)); + + AssertThat(ts_tree_cursor_goto_first_child(cursor), IsTrue()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("[")); + AssertThat(ts_node_start_byte(node), Equals(array_index)); + + // Cannot descend into a node with no children + AssertThat(ts_tree_cursor_goto_first_child(cursor), IsFalse()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("[")); + AssertThat(ts_node_start_byte(node), Equals(array_index)); + + AssertThat(ts_tree_cursor_goto_next_sibling(cursor), IsTrue()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("number")); + AssertThat(ts_node_start_byte(node), Equals(number_index)); + + AssertThat(ts_tree_cursor_goto_next_sibling(cursor), IsTrue()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals(",")); + AssertThat(ts_node_start_byte(node), Equals(number_end_index)); + + AssertThat(ts_tree_cursor_goto_next_sibling(cursor), IsTrue()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("false")); + AssertThat(ts_node_start_byte(node), Equals(false_index)); + + AssertThat(ts_tree_cursor_goto_next_sibling(cursor), IsTrue()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals(",")); + AssertThat(ts_node_start_byte(node), Equals(false_end_index)); + + AssertThat(ts_tree_cursor_goto_next_sibling(cursor), IsTrue()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("object")); + AssertThat(ts_node_start_byte(node), Equals(object_index)); + + AssertThat(ts_tree_cursor_goto_first_child(cursor), IsTrue()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("{")); + AssertThat(ts_node_start_byte(node), Equals(object_index)); + + AssertThat(ts_tree_cursor_goto_next_sibling(cursor), IsTrue()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("pair")); + AssertThat(ts_node_start_byte(node), Equals(string_index)); + + AssertThat(ts_tree_cursor_goto_first_child(cursor), IsTrue()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("string")); + AssertThat(ts_node_start_byte(node), Equals(string_index)); + + AssertThat(ts_tree_cursor_goto_next_sibling(cursor), IsTrue()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals(":")); + AssertThat(ts_node_start_byte(node), Equals(string_end_index)); + + AssertThat(ts_tree_cursor_goto_next_sibling(cursor), IsTrue()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("null")); + AssertThat(ts_node_start_byte(node), Equals(null_index)); + + // Cannot move beyond a node with no next sibling + AssertThat(ts_tree_cursor_goto_next_sibling(cursor), IsFalse()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("null")); + AssertThat(ts_node_start_byte(node), Equals(null_index)); + + AssertThat(ts_tree_cursor_goto_parent(cursor), IsTrue()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("pair")); + AssertThat(ts_node_start_byte(node), Equals(string_index)); + + AssertThat(ts_tree_cursor_goto_parent(cursor), IsTrue()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("object")); + AssertThat(ts_node_start_byte(node), Equals(object_index)); + + AssertThat(ts_tree_cursor_goto_parent(cursor), IsTrue()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("array")); + AssertThat(ts_node_start_byte(node), Equals(array_index)); + + AssertThat(ts_tree_cursor_goto_parent(cursor), IsTrue()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("value")); + AssertThat(ts_node_start_byte(node), Equals(array_index)); + + // The root node doesn't have a parent. + AssertThat(ts_tree_cursor_goto_parent(cursor), IsFalse()); + node = ts_tree_cursor_current_node(cursor); + AssertThat(ts_node_type(node), Equals("value")); + AssertThat(ts_node_start_byte(node), Equals(array_index)); + }); +}); + END_TEST