diff --git a/include/tree_sitter/parser/stack.h b/include/tree_sitter/parser/stack.h index 4800ec92..79d5b2fa 100644 --- a/include/tree_sitter/parser/stack.h +++ b/include/tree_sitter/parser/stack.h @@ -1,6 +1,10 @@ #ifndef TREE_SITTER_PARSER_STACK_H_ #define TREE_SITTER_PARSER_STACK_H_ +#ifdef __cplusplus +extern "C" { +#endif + #include "tree_sitter/runtime.h" typedef unsigned short ts_state_id; @@ -14,6 +18,7 @@ typedef struct { } ts_stack; ts_stack ts_stack_make(); +void ts_stack_delete(ts_stack *); ts_tree * ts_stack_reduce(ts_stack *stack, ts_symbol symbol, size_t immediate_child_count, const int *hidden_symbol_flags, const int *ubiquitous_symbol_flags); void ts_stack_shrink(ts_stack *stack, size_t new_size); void ts_stack_push(ts_stack *stack, ts_state_id state, ts_tree *node); @@ -21,4 +26,8 @@ ts_state_id ts_stack_top_state(const ts_stack *stack); ts_tree * ts_stack_top_node(const ts_stack *stack); size_t ts_stack_right_position(const ts_stack *stack); +#ifdef __cplusplus +} +#endif + #endif \ No newline at end of file diff --git a/spec/runtime/helpers/tree_helpers.cc b/spec/runtime/helpers/tree_helpers.cc new file mode 100644 index 00000000..675b668b --- /dev/null +++ b/spec/runtime/helpers/tree_helpers.cc @@ -0,0 +1,8 @@ +#include "helpers/tree_helpers.h" + +ts_tree ** tree_array(std::vector trees) { + ts_tree ** result = (ts_tree **)calloc(trees.size(), sizeof(ts_tree *)); + for (size_t i = 0; i < trees.size(); i++) + result[i] = trees[i]; + return result; +} diff --git a/spec/runtime/helpers/tree_helpers.h b/spec/runtime/helpers/tree_helpers.h new file mode 100644 index 00000000..383d066f --- /dev/null +++ b/spec/runtime/helpers/tree_helpers.h @@ -0,0 +1,5 @@ +#include "tree_sitter/runtime.h" + +#include + +ts_tree ** tree_array(std::vector trees); \ No newline at end of file diff --git a/spec/runtime/runtime_spec_helper.h b/spec/runtime/runtime_spec_helper.h index e3262b2b..a62e5b59 100644 --- a/spec/runtime/runtime_spec_helper.h +++ b/spec/runtime/runtime_spec_helper.h @@ -3,6 +3,7 @@ #include "bandit/bandit.h" #include "tree_sitter/runtime.h" +#include "helpers/tree_helpers.h" using namespace std; using namespace bandit; diff --git a/spec/runtime/stack_spec.cc b/spec/runtime/stack_spec.cc new file mode 100644 index 00000000..a9fe8724 --- /dev/null +++ b/spec/runtime/stack_spec.cc @@ -0,0 +1,159 @@ +#include "runtime_spec_helper.h" +#include "tree_sitter/parser/stack.h" + +START_TEST + +enum { + sym1 = 101, + sym2 = 102, + hidden_sym = 103, +}; + +int hidden_symbols[] = { + [sym1] = 0, + [sym2] = 0, + [hidden_sym] = 1, +}; + +describe("stacks", [&]() { + ts_stack stack; + + before_each([&]() { + stack = ts_stack_make(); + }); + + after_each([&]() { + ts_stack_delete(&stack); + }); + + it("starts out empty", [&]() { + AssertThat(stack.size, Equals(0)); + AssertThat(ts_stack_top_state(&stack), Equals(0)); + AssertThat(ts_stack_top_node(&stack), Equals((ts_tree *)nullptr)); + }); + + describe("pushing a symbol", [&]() { + ts_tree *node1; + + before_each([&]() { + node1 = ts_tree_make_leaf(sym1, 5, 1); + ts_stack_push(&stack, 5, node1); + }); + + after_each([&]() { + ts_tree_release(node1); + }); + + it("adds the symbol to the stack", [&]() { + AssertThat(stack.size, Equals(1)); + AssertThat(ts_stack_top_state(&stack), Equals(5)); + AssertThat(ts_stack_top_node(&stack), Equals(node1)); + }); + }); + + describe("reducing a symbol", [&]() { + ts_tree **nodes; + + before_each([&]() { + nodes = tree_array({ + ts_tree_make_leaf(sym1, 5, 1), + ts_tree_make_leaf(sym1, 5, 1), + ts_tree_make_leaf(hidden_sym, 5, 1), + ts_tree_make_leaf(sym1, 5, 1), + }); + + for (ts_state_id i = 0; i < 4; i++) + ts_stack_push(&stack, 10 + i, nodes[i]); + }); + + after_each([&]() { + for (ts_state_id i = 0; i < 4; i++) + ts_tree_release(nodes[i]); + free(nodes); + }); + + it("pops the given number of nodes off the stack", [&]() { + AssertThat(stack.size, Equals(4)); + ts_stack_reduce(&stack, sym2, 3, hidden_symbols, nullptr); + AssertThat(stack.size, Equals(1)); + }); + + it("returns a node with the given symbol", [&]() { + ts_tree *node = ts_stack_reduce(&stack, sym2, 3, hidden_symbols, nullptr); + AssertThat(ts_tree_symbol(node), Equals(sym2)); + }); + + it("makes all of the removed nodes immediate children of the new node", [&]() { + ts_tree *expected_children[3] = { + stack.entries[1].node, + stack.entries[2].node, + stack.entries[3].node, + }; + + ts_tree *node = ts_stack_reduce(&stack, sym2, 3, hidden_symbols, nullptr); + size_t immediate_child_count; + ts_tree **immediate_children = ts_tree_immediate_children(node, &immediate_child_count); + + AssertThat(immediate_child_count, Equals(3)); + for (size_t i = 0; i < 3; i++) + AssertThat(immediate_children[i], Equals(expected_children[i])); + }); + + it("removes any hidden nodes from its regular list of children", [&]() { + ts_tree *expected_children[2] = { + stack.entries[1].node, + stack.entries[3].node, + }; + + ts_tree *node = ts_stack_reduce(&stack, sym2, 3, hidden_symbols, nullptr); + size_t child_count; + ts_tree **children = ts_tree_children(node, &child_count); + + AssertThat(child_count, Equals(2)); + for (size_t i = 0; i < 2; i++) + AssertThat(children[i], Equals(expected_children[i])); + }); + + describe("when there are hidden nodes with children of their own", [&]() { + ts_tree **grandchildren; + ts_tree *hidden_node; + + before_each([&]() { + grandchildren = tree_array({ + ts_tree_make_leaf(sym1, 10, 2), + ts_tree_make_leaf(sym2, 10, 2), + }); + + hidden_node = ts_tree_make_node(hidden_sym, 2, 0, grandchildren); + ts_stack_push(&stack, 21, hidden_node); + }); + + after_each([&]() { + for (ts_state_id i = 0; i < 2; i++) + ts_tree_release(grandchildren[i]); + free(grandchildren); + ts_tree_release(hidden_node); + }); + + it("makes those child nodes children of the new node", [&]() { + ts_tree *node = ts_stack_reduce(&stack, sym2, 4, hidden_symbols, nullptr); + + ts_tree *expected_children[4] = { + stack.entries[1].node, + stack.entries[3].node, + grandchildren[0], + grandchildren[1], + }; + + size_t child_count; + ts_tree **children = ts_tree_children(node, &child_count); + + AssertThat(child_count, Equals(4)); + for (size_t i = 0; i < 4; i++) + AssertThat(children[i], Equals(expected_children[i])); + }); + }); + }); +}); + +END_TEST diff --git a/spec/runtime/tree_spec.cc b/spec/runtime/tree_spec.cc index 11337be8..a9fb10f7 100644 --- a/spec/runtime/tree_spec.cc +++ b/spec/runtime/tree_spec.cc @@ -1,12 +1,5 @@ #include "runtime_spec_helper.h" -static ts_tree ** tree_array(vector trees) { - ts_tree ** result = (ts_tree **)calloc(trees.size(), sizeof(ts_tree *)); - for (size_t i = 0; i < trees.size(); i++) - result[i] = trees[i]; - return result; -} - START_TEST enum { diff --git a/src/runtime/stack.c b/src/runtime/stack.c index 6065d185..0d085edb 100644 --- a/src/runtime/stack.c +++ b/src/runtime/stack.c @@ -13,6 +13,11 @@ ts_stack ts_stack_make() { return result; } +void ts_stack_delete(ts_stack *stack) { + ts_stack_shrink(stack, 0); + free(stack->entries); +} + ts_state_id ts_stack_top_state(const ts_stack *stack) { if (stack->size == 0) return INITIAL_STATE; return stack->entries[stack->size - 1].state; @@ -27,6 +32,7 @@ void ts_stack_push(ts_stack *stack, ts_state_id state, ts_tree *node) { stack->entries[stack->size].state = state; stack->entries[stack->size].node = node; stack->size++; + ts_tree_retain(node); } void ts_stack_shrink(ts_stack *stack, size_t new_size) {