From 0467d190fe91841dcda37cfa167280e889f21870 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 15 Sep 2015 16:00:16 -0700 Subject: [PATCH] Add ts_tree_edit function --- spec/runtime/tree_spec.cc | 192 ++++++++++++++++++++++++++++++-------- src/runtime/length.h | 8 ++ src/runtime/lexer.c | 2 +- src/runtime/tree.c | 72 +++++++++++++- src/runtime/tree.h | 2 + 5 files changed, 234 insertions(+), 42 deletions(-) diff --git a/spec/runtime/tree_spec.cc b/spec/runtime/tree_spec.cc index d82c5ef8..5f536ad8 100644 --- a/spec/runtime/tree_spec.cc +++ b/spec/runtime/tree_spec.cc @@ -6,9 +6,12 @@ START_TEST enum { - cat = ts_builtin_sym_start, - dog, - pig, + cat = ts_builtin_sym_start, + dog, + eel, + fox, + goat, + hog, }; static const char *names[] = { @@ -16,30 +19,22 @@ static const char *names[] = { "END", "cat", "dog", - "pig", + "eel", + "fox", + "goat", + "hog", }; describe("Tree", []() { TSTree *tree1, *tree2, *parent1; before_each([&]() { - tree1 = ts_tree_make_leaf( - cat, - ts_length_make(5, 4), - ts_length_make(2, 1), - TSNodeTypeNamed); - - tree2 = ts_tree_make_leaf( - cat, - ts_length_make(3, 3), - ts_length_make(1, 1), - TSNodeTypeNamed); - - parent1 = ts_tree_make_node( - dog, - 2, - tree_array({ tree1, tree2, }), - TSNodeTypeNamed); + tree1 = ts_tree_make_leaf(cat, {2, 1}, {5, 4}, TSNodeTypeNamed); + tree2 = ts_tree_make_leaf(cat, {1, 1}, {3, 3}, TSNodeTypeNamed); + parent1 = ts_tree_make_node(dog, 2, tree_array({ + tree1, + tree2, + }), TSNodeTypeNamed); }); after_each([&]() { @@ -86,7 +81,7 @@ describe("Tree", []() { before_each([&]() { ts_tree_set_fragile_left(tree1); ts_tree_set_extra(tree1); - parent = ts_tree_make_node(pig, 2, tree_array({ + parent = ts_tree_make_node(eel, 2, tree_array({ tree1, tree2, }), TSNodeTypeNamed); @@ -107,7 +102,7 @@ describe("Tree", []() { before_each([&]() { ts_tree_set_fragile_right(tree2); ts_tree_set_extra(tree2); - parent = ts_tree_make_node(pig, 2, tree_array({ + parent = ts_tree_make_node(eel, 2, tree_array({ tree1, tree2, }), TSNodeTypeNamed); @@ -128,7 +123,7 @@ describe("Tree", []() { before_each([&]() { ts_tree_set_fragile_right(tree1); ts_tree_set_fragile_left(tree2); - parent = ts_tree_make_node(pig, 2, tree_array({ + parent = ts_tree_make_node(eel, 2, tree_array({ tree1, tree2, }), TSNodeTypeNamed); @@ -145,26 +140,141 @@ describe("Tree", []() { }); }); + describe("edit(InputEdit)", [&]() { + TSTree *tree = nullptr; + + before_each([&]() { + tree = ts_tree_make_node(cat, 3, tree_array({ + ts_tree_make_leaf(dog, {2, 2}, {3, 3}, TSNodeTypeNamed), + ts_tree_make_leaf(eel, {2, 2}, {3, 3}, TSNodeTypeNamed), + ts_tree_make_leaf(fox, {2, 2}, {3, 3}, TSNodeTypeNamed), + }), TSNodeTypeNamed); + + AssertThat(tree->padding, Equals({2, 2})); + AssertThat(tree->size, Equals({13, 13})); + }); + + after_each([&]() { + ts_tree_release(tree); + }); + + auto assert_consistent = [&](const TSTree *tree) { + AssertThat(tree->children[0]->padding, Equals(tree->padding)); + + TSLength total_children_size = ts_length_zero(); + for (size_t i = 0; i < tree->child_count; i++) + total_children_size = ts_length_add(total_children_size, ts_tree_total_size(tree->children[i])); + AssertThat(total_children_size, Equals(ts_tree_total_size(tree))); + }; + + describe("edits within a tree's padding", [&]() { + it("resizes the padding of the tree and its leftmost descendants", [&]() { + ts_tree_edit(tree, {1, 1, 0}); + + assert_consistent(tree); + + AssertThat(tree->options.has_changes, IsTrue()); + AssertThat(tree->padding, Equals({0, 3})); + AssertThat(tree->size, Equals({13, 13})); + + AssertThat(tree->children[0]->options.has_changes, IsTrue()); + AssertThat(tree->children[0]->padding, Equals({0, 3})); + AssertThat(tree->children[0]->size, Equals({3, 3})); + + AssertThat(tree->children[1]->options.has_changes, IsFalse()); + AssertThat(tree->children[1]->padding, Equals({2, 2})); + AssertThat(tree->children[1]->size, Equals({3, 3})); + }); + }); + + describe("edits that start in a tree's padding but extend into its content", [&]() { + it("shrinks the content to compensate for the expanded padding", [&]() { + ts_tree_edit(tree, {1, 4, 3}); + + assert_consistent(tree); + + AssertThat(tree->options.has_changes, IsTrue()); + AssertThat(tree->padding, Equals({0, 5})); + AssertThat(tree->size, Equals({0, 11})); + + AssertThat(tree->children[0]->options.has_changes, IsTrue()); + AssertThat(tree->children[0]->padding, Equals({0, 5})); + AssertThat(tree->children[0]->size, Equals({0, 1})); + }); + }); + + describe("insertions at the edge of a tree's padding", [&]() { + it("expands the tree's padding", [&]() { + ts_tree_edit(tree, {2, 2, 0}); + + assert_consistent(tree); + + AssertThat(tree->options.has_changes, IsTrue()); + AssertThat(tree->padding, Equals({0, 4})); + AssertThat(tree->size, Equals({13, 13})); + + AssertThat(tree->children[0]->options.has_changes, IsTrue()); + AssertThat(tree->children[0]->padding, Equals({0, 4})); + AssertThat(tree->children[0]->size, Equals({3, 3})); + + AssertThat(tree->children[1]->options.has_changes, IsFalse()); + }); + }); + + describe("replacements starting at the edge of a tree's padding", [&]() { + it("resizes the content and not the padding", [&]() { + ts_tree_edit(tree, {2, 5, 2}); + + assert_consistent(tree); + + AssertThat(tree->options.has_changes, IsTrue()); + AssertThat(tree->padding, Equals({2, 2})); + AssertThat(tree->size, Equals({0, 16})); + + AssertThat(tree->children[0]->options.has_changes, IsTrue()); + AssertThat(tree->children[0]->padding, Equals({2, 2})); + AssertThat(tree->children[0]->size, Equals({0, 6})); + + AssertThat(tree->children[1]->options.has_changes, IsFalse()); + }); + }); + + describe("deletions that span more than one child node", [&]() { + it("shrinks subsequent child nodes", [&]() { + ts_tree_edit(tree, {1, 3, 10}); + + assert_consistent(tree); + + AssertThat(tree->options.has_changes, IsTrue()); + AssertThat(tree->padding, Equals({0, 4})); + AssertThat(tree->size, Equals({0, 4})); + + AssertThat(tree->children[0]->options.has_changes, IsTrue()); + AssertThat(tree->children[0]->padding, Equals({0, 4})); + AssertThat(tree->children[0]->size, Equals({0, 0})); + + AssertThat(tree->children[1]->options.has_changes, IsTrue()); + AssertThat(tree->children[1]->padding, Equals({0, 0})); + AssertThat(tree->children[1]->size, Equals({0, 0})); + + AssertThat(tree->children[2]->options.has_changes, IsTrue()); + AssertThat(tree->children[2]->padding, Equals({0, 1})); + AssertThat(tree->children[2]->size, Equals({3, 3})); + }); + }); + }); + describe("equality", [&]() { it("returns true for identical trees", [&]() { - TSTree *tree1_copy = ts_tree_make_leaf( - cat, - ts_length_make(5, 4), - ts_length_make(2, 1), - TSNodeTypeNamed); - + TSTree *tree1_copy = ts_tree_make_leaf(cat, {2, 1}, {5, 4}, TSNodeTypeNamed); AssertThat(ts_tree_eq(tree1, tree1_copy), IsTrue()); - TSTree *tree2_copy = ts_tree_make_leaf( - cat, - ts_length_make(3, 3), - ts_length_make(1, 1), - TSNodeTypeNamed); - + TSTree *tree2_copy = ts_tree_make_leaf(cat, {1, 1}, {3, 3}, TSNodeTypeNamed); AssertThat(ts_tree_eq(tree2, tree2_copy), IsTrue()); TSTree *parent2 = ts_tree_make_node(dog, 2, tree_array({ - tree1_copy, tree2_copy, + tree1_copy, + tree2_copy, }), TSNodeTypeNamed); AssertThat(ts_tree_eq(parent1, parent2), IsTrue()); @@ -177,8 +287,8 @@ describe("Tree", []() { it("returns false for trees with different symbols", [&]() { TSTree *different_tree = ts_tree_make_leaf( tree1->symbol + 1, - tree1->size, tree1->padding, + tree1->size, TSNodeTypeNamed); AssertThat(ts_tree_eq(tree1, different_tree), IsFalse()); @@ -188,8 +298,8 @@ describe("Tree", []() { it("returns false for trees with different children", [&]() { TSTree *different_tree = ts_tree_make_leaf( tree1->symbol + 1, - tree1->size, tree1->padding, + tree1->size, TSNodeTypeNamed); TSTree *different_parent = ts_tree_make_node(dog, 2, tree_array({ @@ -243,6 +353,10 @@ describe("Tree", []() { END_TEST +ostream &operator<<(ostream &stream, const TSLength &length) { + return stream << "{bytes:" << length.bytes << ", chars:" << length.chars << "}"; +} + bool operator==(TSLength left, TSLength right) { return ts_length_eq(left, right); } diff --git a/src/runtime/length.h b/src/runtime/length.h index e00e4679..58e078e4 100644 --- a/src/runtime/length.h +++ b/src/runtime/length.h @@ -8,6 +8,10 @@ static inline TSLength ts_length_add(TSLength len1, TSLength len2) { TSLength result; result.bytes = len1.bytes + len2.bytes; result.chars = len1.chars + len2.chars; + + if ((len1.chars > 0 && len1.bytes == 0) || (len2.chars > 0 && len2.bytes == 0)) + result.bytes = 0; + return result; } @@ -15,6 +19,10 @@ static inline TSLength ts_length_sub(TSLength len1, TSLength len2) { TSLength result; result.bytes = len1.bytes - len2.bytes; result.chars = len1.chars - len2.chars; + + if ((len1.chars > 0 && len1.bytes == 0) || (len2.chars > 0 && len2.bytes == 0)) + result.bytes = 0; + return result; } diff --git a/src/runtime/lexer.c b/src/runtime/lexer.c index cfc1f3ac..adcdbdcf 100644 --- a/src/runtime/lexer.c +++ b/src/runtime/lexer.c @@ -81,7 +81,7 @@ static TSTree *ts_lexer__accept(TSLexer *lexer, TSSymbol symbol, return ts_tree_make_error(size, padding, lexer->lookahead); } else { DEBUG("accept_token sym:%s", symbol_name); - return ts_tree_make_leaf(symbol, size, padding, node_type); + return ts_tree_make_leaf(symbol, padding, size, node_type); } } diff --git a/src/runtime/tree.c b/src/runtime/tree.c index 7e82a67b..783fa02f 100644 --- a/src/runtime/tree.c +++ b/src/runtime/tree.c @@ -6,7 +6,7 @@ #include "runtime/tree.h" #include "runtime/length.h" -TSTree *ts_tree_make_leaf(TSSymbol sym, TSLength size, TSLength padding, +TSTree *ts_tree_make_leaf(TSSymbol sym, TSLength padding, TSLength size, TSNodeType node_type) { TSTree *result = malloc(sizeof(TSTree)); *result = (TSTree){ @@ -31,7 +31,7 @@ TSTree *ts_tree_make_leaf(TSSymbol sym, TSLength size, TSLength padding, TSTree *ts_tree_make_error(TSLength size, TSLength padding, char lookahead_char) { TSTree *result = - ts_tree_make_leaf(ts_builtin_sym_error, size, padding, TSNodeTypeNamed); + ts_tree_make_leaf(ts_builtin_sym_error, padding, size, TSNodeTypeNamed); result->lookahead_char = lookahead_char; return result; } @@ -194,3 +194,71 @@ void ts_tree_prepend_children(TSTree *tree, size_t count, TSTree **children) { ts_tree__set_children(tree, new_children, new_child_count); } + +static inline long min(long a, long b) { + return a <= b ? a : b; +} + +void ts_tree_edit(TSTree *tree, TSInputEdit edit) { + size_t start = edit.position; + size_t old_end = edit.position + edit.chars_removed; + size_t new_end = edit.position + edit.chars_inserted; + + assert(start >= 0); + assert(old_end <= ts_tree_total_size(tree).chars); + + tree->options.has_changes = true; + + if (start < tree->padding.chars) { + tree->padding.bytes = 0; + long remaining_padding = tree->padding.chars - old_end; + if (remaining_padding >= 0) { + tree->padding.chars = new_end + remaining_padding; + } else { + tree->padding.chars = new_end; + tree->size.chars += remaining_padding; + tree->size.bytes = 0; + } + } else if (start == tree->padding.chars && edit.chars_removed == 0) { + tree->padding.bytes = 0; + tree->padding.chars += edit.chars_inserted; + } else { + tree->size.bytes = 0; + tree->size.chars += (edit.chars_inserted - edit.chars_removed); + } + + bool found_first_child = false; + long remainder_to_delete = edit.chars_removed - edit.chars_inserted; + size_t child_left = 0, child_right = 0; + for (size_t i = 0; i < tree->child_count; i++) { + TSTree *child = tree->children[i]; + size_t child_size = ts_tree_total_size(child).chars; + child_left = child_right; + child_right += child_size; + + if (!found_first_child) { + if (child_right >= start) { + found_first_child = true; + size_t chars_removed = min(edit.chars_removed, child_right - start); + remainder_to_delete -= (chars_removed - edit.chars_inserted); + ts_tree_edit(child, (TSInputEdit){ + .position = start - child_left, + .chars_inserted = edit.chars_inserted, + .chars_removed = chars_removed, + }); + } + } else { + if (remainder_to_delete > 0) { + size_t chars_removed = min(remainder_to_delete, child_size); + remainder_to_delete -= chars_removed; + ts_tree_edit(child, (TSInputEdit){ + .position = 0, + .chars_inserted = 0, + .chars_removed = chars_removed, + }); + } else { + break; + } + } + } +} diff --git a/src/runtime/tree.h b/src/runtime/tree.h index 65c45324..b7aea878 100644 --- a/src/runtime/tree.h +++ b/src/runtime/tree.h @@ -29,6 +29,7 @@ struct TSTree { bool extra : 1; bool fragile_left : 1; bool fragile_right : 1; + bool has_changes : 1; } options; unsigned short int ref_count; }; @@ -76,6 +77,7 @@ char *ts_tree_string(const TSTree *tree, const char **names); char *ts_tree_error_string(const TSTree *tree, const char **names); TSLength ts_tree_total_size(const TSTree *tree); void ts_tree_prepend_children(TSTree *, size_t, TSTree **); +void ts_tree_edit(TSTree *, TSInputEdit); static inline bool ts_tree_is_empty(TSTree *tree) { return ts_tree_total_size(tree).bytes == 0;