Rename spec -> test

'Test' is a lot more straightforward of a name.
This commit is contained in:
Max Brunsfeld 2017-03-09 20:40:01 -08:00
parent 7d8daf573e
commit 6dc0ff359d
109 changed files with 44 additions and 44 deletions

View file

@ -0,0 +1,372 @@
#include "test_helper.h"
#include "runtime/alloc.h"
#include "helpers/record_alloc.h"
#include "helpers/stream_methods.h"
#include "helpers/tree_helpers.h"
#include "helpers/point_helpers.h"
#include "helpers/spy_logger.h"
#include "helpers/stderr_logger.h"
#include "helpers/spy_input.h"
#include "helpers/load_language.h"
TSPoint point(size_t row, size_t column) {
return TSPoint{static_cast<uint32_t>(row), static_cast<uint32_t>(column)};
}
START_TEST
describe("Document", [&]() {
TSDocument *document;
TSNode root;
before_each([&]() {
record_alloc::start();
document = ts_document_new();
});
after_each([&]() {
ts_document_free(document);
record_alloc::stop();
AssertThat(record_alloc::outstanding_allocation_indices(), IsEmpty());
});
auto assert_node_string_equals = [&](TSNode node, const string &expected) {
char *str = ts_node_string(node, document);
string actual(str);
ts_free(str);
AssertThat(actual, Equals(expected));
};
describe("set_input(input)", [&]() {
SpyInput *spy_input;
before_each([&]() {
spy_input = new SpyInput("{\"key\": [null, 2]}", 3);
ts_document_set_language(document, load_real_language("json"));
ts_document_set_input_string(document, "{\"key\": [1, 2]}");
ts_document_parse(document);
root = ts_document_root_node(document);
assert_node_string_equals(
root,
"(object (pair (string) (array (number) (number))))");
});
after_each([&]() {
delete spy_input;
});
it("handles both UTF8 and UTF16 encodings", [&]() {
const char16_t content[] = u"[true, false]";
spy_input->content = string((const char *)content, sizeof(content));
spy_input->encoding = TSInputEncodingUTF16;
ts_document_set_input(document, spy_input->input());
ts_document_invalidate(document);
ts_document_parse(document);
root = ts_document_root_node(document);
assert_node_string_equals(
root,
"(array (true) (false))");
});
it("allows columns to be measured in either bytes or characters", [&]() {
const char16_t content[] = u"[true, false]";
spy_input->content = string((const char *)content, sizeof(content));
spy_input->encoding = TSInputEncodingUTF16;
// spy_input->measure_columns_in_bytes
ts_document_set_input(document, spy_input->input());
ts_document_invalidate(document);
ts_document_parse(document);
});
it("allows the input to be retrieved later", [&]() {
ts_document_set_input(document, spy_input->input());
AssertThat(ts_document_input(document).payload, Equals<void *>(spy_input));
AssertThat(ts_document_input(document).read, Equals(spy_input->input().read));
AssertThat(ts_document_input(document).seek, Equals(spy_input->input().seek));
});
it("does not assume that the document's text has changed", [&]() {
ts_document_set_input(document, spy_input->input());
AssertThat(ts_document_root_node(document), Equals<TSNode>(root));
AssertThat(ts_node_has_changes(root), IsFalse());
AssertThat(spy_input->strings_read, Equals(vector<string>({ "" })));
});
it("reads text from the new input for future parses", [&]() {
ts_document_set_input(document, spy_input->input());
// Insert 'null', delete '1'.
TSInputEdit edit = {};
edit.start_point.column = edit.start_byte = strlen("{\"key\": [");
edit.extent_added.column = edit.bytes_added = 4;
edit.extent_removed.column = edit.bytes_removed = 1;
ts_document_edit(document, edit);
ts_document_parse(document);
TSNode new_root = ts_document_root_node(document);
assert_node_string_equals(
new_root,
"(object (pair (string) (array (null) (number))))");
AssertThat(spy_input->strings_read, Equals(vector<string>({" [null, 2" })));
});
it("allows setting input string with length", [&]() {
const char content[] = { '1' };
ts_document_set_input_string_with_length(document, content, 1);
ts_document_parse(document);
TSNode new_root = ts_document_root_node(document);
AssertThat(ts_node_end_char(new_root), Equals<size_t>(1));
assert_node_string_equals(
new_root,
"(number)");
});
it("reads from the new input correctly when the old input was blank", [&]() {
ts_document_set_input_string(document, "");
ts_document_parse(document);
TSNode new_root = ts_document_root_node(document);
AssertThat(ts_node_end_char(new_root), Equals<size_t>(0));
assert_node_string_equals(
new_root,
"(ERROR)");
ts_document_set_input_string(document, "1");
ts_document_parse(document);
new_root = ts_document_root_node(document);
AssertThat(ts_node_end_char(new_root), Equals<size_t>(1));
assert_node_string_equals(
new_root,
"(number)");
});
});
describe("set_language(language)", [&]() {
before_each([&]() {
ts_document_set_input_string(document, "{\"key\": [1, 2]}\n");
});
it("uses the given language for future parses", [&]() {
ts_document_set_language(document, load_real_language("json"));
ts_document_parse(document);
root = ts_document_root_node(document);
assert_node_string_equals(
root,
"(object (pair (string) (array (number) (number))))");
});
it("clears out any previous tree", [&]() {
ts_document_set_language(document, load_real_language("json"));
ts_document_parse(document);
ts_document_set_language(document, load_real_language("javascript"));
AssertThat(ts_document_root_node(document).data, Equals<void *>(nullptr));
ts_document_parse(document);
root = ts_document_root_node(document);
assert_node_string_equals(
root,
"(program (expression_statement "
"(object (pair (string) (array (number) (number))))))");
});
it("does not allow setting a language with a different version number", [&]() {
TSLanguage language = *load_real_language("json");
AssertThat(ts_language_version(&language), Equals<uint32_t>(TREE_SITTER_LANGUAGE_VERSION));
language.version++;
AssertThat(ts_language_version(&language), !Equals<uint32_t>(TREE_SITTER_LANGUAGE_VERSION));
ts_document_set_language(document, &language);
AssertThat(ts_document_language(document), IsNull());
});
});
describe("set_logger(TSLogger)", [&]() {
SpyLogger *logger;
before_each([&]() {
logger = new SpyLogger();
ts_document_set_language(document, load_real_language("json"));
ts_document_set_input_string(document, "[1, 2]");
});
after_each([&]() {
delete logger;
});
it("calls the debugger with a message for each parse action", [&]() {
ts_document_set_logger(document, logger->logger());
ts_document_parse(document);
AssertThat(logger->messages, Contains("new_parse"));
AssertThat(logger->messages, Contains("skip character:' '"));
AssertThat(logger->messages, Contains("consume character:'['"));
AssertThat(logger->messages, Contains("consume character:'1'"));
AssertThat(logger->messages, Contains("reduce sym:array, child_count:4"));
AssertThat(logger->messages, Contains("accept"));
});
it("allows the debugger to be retrieved later", [&]() {
ts_document_set_logger(document, logger->logger());
AssertThat(ts_document_logger(document).payload, Equals(logger));
});
describe("disabling debugging", [&]() {
before_each([&]() {
ts_document_set_logger(document, logger->logger());
ts_document_set_logger(document, {NULL, NULL});
});
it("does not call the debugger any more", [&]() {
ts_document_parse(document);
AssertThat(logger->messages, IsEmpty());
});
});
});
describe("parse_and_get_changed_ranges()", [&]() {
SpyInput *input;
before_each([&]() {
ts_document_set_language(document, load_real_language("javascript"));
input = new SpyInput("{a: null};", 3);
ts_document_set_input(document, input->input());
ts_document_parse(document);
assert_node_string_equals(
ts_document_root_node(document),
"(program (expression_statement (object (pair (identifier) (null)))))");
});
after_each([&]() {
delete input;
});
auto get_invalidated_ranges_for_edit = [&](std::function<TSInputEdit()> callback) -> vector<TSRange> {
TSInputEdit edit = callback();
ts_document_edit(document, edit);
TSRange *ranges;
uint32_t range_count = 0;
ts_document_parse_and_get_changed_ranges(document, &ranges, &range_count);
vector<TSRange> result;
for (size_t i = 0; i < range_count; i++) {
result.push_back(ranges[i]);
}
ts_free(ranges);
return result;
};
it("reports changes when one token has been updated", [&]() {
// Replace `null` with `nothing`
auto ranges = get_invalidated_ranges_for_edit([&]() {
return input->replace(input->content.find("ull"), 1, "othing");
});
AssertThat(ranges, Equals(vector<TSRange>({
TSRange{
point(0, input->content.find("nothing")),
point(0, input->content.find("}"))
},
})));
// Replace `nothing` with `null` again
ranges = get_invalidated_ranges_for_edit([&]() {
return input->undo();
});
AssertThat(ranges, Equals(vector<TSRange>({
TSRange{
point(0, input->content.find("null")),
point(0, input->content.find("}"))
},
})));
});
it("reports changes when tokens have been appended", [&]() {
// Add a second key-value pair
auto ranges = get_invalidated_ranges_for_edit([&]() {
return input->replace(input->content.find("}"), 0, ", b: false");
});
AssertThat(ranges, Equals(vector<TSRange>({
TSRange{
point(0, input->content.find(",")),
point(0, input->content.find("}"))
},
})));
// Add a third key-value pair in between the first two
ranges = get_invalidated_ranges_for_edit([&]() {
return input->replace(input->content.find(", b"), 0, ", c: 1");
});
assert_node_string_equals(
ts_document_root_node(document),
"(program (expression_statement (object "
"(pair (identifier) (null)) "
"(pair (identifier) (number)) "
"(pair (identifier) (false)))))");
AssertThat(ranges, Equals(vector<TSRange>({
TSRange{
point(0, input->content.find(", c")),
point(0, input->content.find(", b"))
},
})));
// Delete the middle pair.
ranges = get_invalidated_ranges_for_edit([&]() {
return input->undo();
});
assert_node_string_equals(
ts_document_root_node(document),
"(program (expression_statement (object "
"(pair (identifier) (null)) "
"(pair (identifier) (false)))))");
AssertThat(ranges, IsEmpty());
// Delete the second pair.
ranges = get_invalidated_ranges_for_edit([&]() {
return input->undo();
});
assert_node_string_equals(
ts_document_root_node(document),
"(program (expression_statement (object "
"(pair (identifier) (null)))))");
AssertThat(ranges, IsEmpty());
});
it("reports changes when trees have been wrapped", [&]() {
// Wrap the object in an assignment expression.
auto ranges = get_invalidated_ranges_for_edit([&]() {
return input->replace(input->content.find("null"), 0, "b === ");
});
assert_node_string_equals(
ts_document_root_node(document),
"(program (expression_statement (object "
"(pair (identifier) (rel_op (identifier) (null))))))");
AssertThat(ranges, Equals(vector<TSRange>({
TSRange{
point(0, input->content.find("b ===")),
point(0, input->content.find("}"))
},
})));
});
});
});
END_TEST

436
test/runtime/node_test.cc Normal file
View file

@ -0,0 +1,436 @@
#include "test_helper.h"
#include "runtime/alloc.h"
#include "helpers/tree_helpers.h"
#include "helpers/point_helpers.h"
#include "helpers/load_language.h"
#include "helpers/record_alloc.h"
#include "helpers/stream_methods.h"
START_TEST
describe("Node", []() {
TSDocument *document;
TSNode array_node;
string input_string =
"\n"
"\n"
"[\n"
" 123,\n"
" false,\n"
" {\n"
" \"x\": null\n"
" }\n"
"]";
size_t array_index = input_string.find("[\n");
size_t array_end_index = input_string.find("]") + 1;
size_t number_index = input_string.find("123");
size_t number_end_index = number_index + string("123").size();
size_t false_index = input_string.find("false");
size_t false_end_index = false_index + string("false").size();
size_t object_index = input_string.find("{");
size_t object_end_index = input_string.find("}") + 1;
size_t string_index = input_string.find("\"x\"");
size_t string_end_index = string_index + 3;
size_t colon_index = input_string.find(":");
size_t null_index = input_string.find("null");
size_t null_end_index = null_index + string("null").size();
before_each([&]() {
record_alloc::start();
document = ts_document_new();
ts_document_set_language(document, load_real_language("json"));
ts_document_set_input_string(document, input_string.c_str());
ts_document_parse(document);
array_node = ts_document_root_node(document);
char *node_string = ts_node_string(array_node, document);
AssertThat(node_string, Equals(
"(array "
"(number) "
"(false) "
"(object (pair (string) (null))))"));
ts_free(node_string);
});
after_each([&]() {
ts_document_free(document);
record_alloc::stop();
AssertThat(record_alloc::outstanding_allocation_indices(), IsEmpty());
});
describe("named_child_count(), named_child(i)", [&]() {
it("returns the named child node at the given index", [&]() {
AssertThat(ts_node_type(array_node, document), Equals("array"));
AssertThat(ts_node_named_child_count(array_node), Equals<size_t>(3));
AssertThat(ts_node_start_byte(array_node), Equals(array_index));
AssertThat(ts_node_end_byte(array_node), Equals(array_end_index));
AssertThat(ts_node_start_char(array_node), Equals(array_index));
AssertThat(ts_node_end_char(array_node), Equals(array_end_index));
AssertThat(ts_node_start_point(array_node), Equals<TSPoint>({ 2, 0 }));
AssertThat(ts_node_end_point(array_node), Equals<TSPoint>({ 8, 1 }));
TSNode number_node = ts_node_named_child(array_node, 0);
TSNode false_node = ts_node_named_child(array_node, 1);
TSNode object_node = ts_node_named_child(array_node, 2);
AssertThat(ts_node_type(number_node, document), Equals("number"));
AssertThat(ts_node_type(false_node, document), Equals("false"));
AssertThat(ts_node_type(object_node, document), Equals("object"));
AssertThat(ts_node_start_byte(number_node), Equals(number_index));
AssertThat(ts_node_end_byte(number_node), Equals(number_end_index));
AssertThat(ts_node_start_char(number_node), Equals(number_index));
AssertThat(ts_node_end_char(number_node), Equals(number_end_index));
AssertThat(ts_node_start_point(number_node), Equals<TSPoint>({ 3, 2 }));
AssertThat(ts_node_end_point(number_node), Equals<TSPoint>({ 3, 5 }));
AssertThat(ts_node_start_byte(false_node), Equals(false_index));
AssertThat(ts_node_end_byte(false_node), Equals(false_end_index));
AssertThat(ts_node_start_point(false_node), Equals<TSPoint>({ 4, 2 }));
AssertThat(ts_node_end_point(false_node), Equals<TSPoint>({ 4, 7 }));
AssertThat(ts_node_start_byte(object_node), Equals(object_index));
AssertThat(ts_node_end_byte(object_node), Equals(object_end_index));
AssertThat(ts_node_start_point(object_node), Equals<TSPoint>({ 5, 2 }));
AssertThat(ts_node_end_point(object_node), Equals<TSPoint>({ 7, 3 }));
AssertThat(ts_node_named_child_count(object_node), Equals<size_t>(1));
TSNode pair_node = ts_node_named_child(object_node, 0);
AssertThat(ts_node_type(pair_node, document), Equals("pair"));
AssertThat(ts_node_start_byte(pair_node), Equals(string_index));
AssertThat(ts_node_end_byte(pair_node), Equals(null_end_index));
AssertThat(ts_node_start_point(pair_node), Equals<TSPoint>({ 6, 4 }));
AssertThat(ts_node_end_point(pair_node), Equals<TSPoint>({ 6, 13 }));
AssertThat(ts_node_named_child_count(pair_node), Equals<size_t>(2));
TSNode string_node = ts_node_named_child(pair_node, 0);
TSNode null_node = ts_node_named_child(pair_node, 1);
AssertThat(ts_node_type(string_node, document), Equals("string"));
AssertThat(ts_node_type(null_node, document), Equals("null"));
AssertThat(ts_node_start_byte(string_node), Equals(string_index));
AssertThat(ts_node_end_byte(string_node), Equals(string_end_index));
AssertThat(ts_node_start_point(string_node), Equals<TSPoint>({ 6, 4 }));
AssertThat(ts_node_end_point(string_node), Equals<TSPoint>({ 6, 7 }));
AssertThat(ts_node_start_byte(null_node), Equals(null_index));
AssertThat(ts_node_end_byte(null_node), Equals(null_end_index));
AssertThat(ts_node_start_point(null_node), Equals<TSPoint>({ 6, 9 }));
AssertThat(ts_node_end_point(null_node), Equals<TSPoint>({ 6, 13 }));
AssertThat(ts_node_parent(string_node), Equals(pair_node));
AssertThat(ts_node_parent(null_node), Equals(pair_node));
AssertThat(ts_node_parent(pair_node), Equals(object_node));
AssertThat(ts_node_parent(number_node), Equals(array_node));
AssertThat(ts_node_parent(false_node), Equals(array_node));
AssertThat(ts_node_parent(object_node), Equals(array_node));
AssertThat(ts_node_parent(array_node).data, Equals<void *>(nullptr));
});
});
describe("symbols()", [&]() {
it("returns an iterator that yields each of the node's symbols", [&]() {
const TSLanguage *language = ts_document_language(document);
TSNode false_node = ts_node_descendant_for_char_range(array_node, false_index, false_index + 1);
TSSymbolIterator iterator = ts_node_symbols(false_node);
AssertThat(iterator.done, Equals(false));
AssertThat(ts_language_symbol_name(language, iterator.value), Equals("false"));
ts_symbol_iterator_next(&iterator);
AssertThat(iterator.done, Equals(false));
AssertThat(ts_language_symbol_name(language, iterator.value), Equals("_value"));
ts_symbol_iterator_next(&iterator);
AssertThat(iterator.done, Equals(true));
TSNode comma_node = ts_node_descendant_for_char_range(array_node, number_end_index, number_end_index);
iterator = ts_node_symbols(comma_node);
AssertThat(iterator.done, Equals(false));
AssertThat(ts_language_symbol_name(language, iterator.value), Equals(","));
ts_symbol_iterator_next(&iterator);
AssertThat(iterator.done, Equals(true));
});
});
describe("child_count(), child(i)", [&]() {
it("returns the child node at the given index, including anonymous nodes", [&]() {
AssertThat(ts_node_child_count(array_node), Equals<size_t>(7));
TSNode child1 = ts_node_child(array_node, 0);
TSNode child2 = ts_node_child(array_node, 1);
TSNode child3 = ts_node_child(array_node, 2);
TSNode child4 = ts_node_child(array_node, 3);
TSNode child5 = ts_node_child(array_node, 4);
TSNode child6 = ts_node_child(array_node, 5);
TSNode child7 = ts_node_child(array_node, 6);
AssertThat(ts_node_type(array_node, document), Equals("array"));
AssertThat(ts_node_type(child1, document), Equals("["));
AssertThat(ts_node_type(child2, document), Equals("number"));
AssertThat(ts_node_type(child3, document), Equals(","));
AssertThat(ts_node_type(child4, document), Equals("false"));
AssertThat(ts_node_type(child5, document), Equals(","));
AssertThat(ts_node_type(child6, document), Equals("object"));
AssertThat(ts_node_type(child7, document), Equals("]"));
AssertThat(ts_node_is_named(array_node), IsTrue());
AssertThat(ts_node_is_named(child1), IsFalse());
AssertThat(ts_node_is_named(child2), IsTrue());
AssertThat(ts_node_is_named(child3), IsFalse());
AssertThat(ts_node_is_named(child4), IsTrue());
AssertThat(ts_node_is_named(child5), IsFalse());
AssertThat(ts_node_is_named(child6), IsTrue());
AssertThat(ts_node_is_named(child7), IsFalse());
AssertThat(ts_node_start_byte(child1), Equals(array_index));
AssertThat(ts_node_end_byte(child1), Equals(array_index + 1));
AssertThat(ts_node_start_point(child1), Equals<TSPoint>({ 2, 0 }));
AssertThat(ts_node_end_point(child1), Equals<TSPoint>({ 2, 1 }));
AssertThat(ts_node_start_byte(child3), Equals(number_end_index));
AssertThat(ts_node_end_byte(child3), Equals(number_end_index + 1));
AssertThat(ts_node_start_point(child3), Equals<TSPoint>({ 3, 5 }));
AssertThat(ts_node_end_point(child3), Equals<TSPoint>({ 3, 6 }));
AssertThat(ts_node_start_byte(child5), Equals(false_end_index));
AssertThat(ts_node_end_byte(child5), Equals(false_end_index + 1));
AssertThat(ts_node_start_point(child5), Equals<TSPoint>({ 4, 7 }));
AssertThat(ts_node_end_point(child5), Equals<TSPoint>({ 4, 8 }));
AssertThat(ts_node_start_byte(child7), Equals(array_end_index - 1));
AssertThat(ts_node_end_byte(child7), Equals(array_end_index));
AssertThat(ts_node_start_point(child7), Equals<TSPoint>({ 8, 0 }));
AssertThat(ts_node_end_point(child7), Equals<TSPoint>({ 8, 1 }));
AssertThat(ts_node_child_count(child6), Equals<size_t>(3))
TSNode left_brace = ts_node_child(child6, 0);
TSNode pair = ts_node_child(child6, 1);
TSNode right_brace = ts_node_child(child6, 2);
TSNode grandchild2 = ts_node_child(pair, 0);
TSNode grandchild3 = ts_node_child(pair, 1);
TSNode grandchild4 = ts_node_child(pair, 2);
AssertThat(ts_node_type(left_brace, document), Equals("{"));
AssertThat(ts_node_type(pair, document), Equals("pair"));
AssertThat(ts_node_type(right_brace, document), Equals("}"));
AssertThat(ts_node_type(grandchild2, document), Equals("string"));
AssertThat(ts_node_type(grandchild3, document), Equals(":"));
AssertThat(ts_node_type(grandchild4, document), Equals("null"));
AssertThat(ts_node_parent(grandchild2), Equals(pair));
AssertThat(ts_node_parent(grandchild3), Equals(pair));
AssertThat(ts_node_parent(grandchild4), Equals(pair));
AssertThat(ts_node_parent(left_brace), Equals(child6));
AssertThat(ts_node_parent(pair), Equals(child6));
AssertThat(ts_node_parent(right_brace), Equals(child6));
AssertThat(ts_node_parent(child1), Equals(array_node));
AssertThat(ts_node_parent(child2), Equals(array_node));
AssertThat(ts_node_parent(child3), Equals(array_node));
AssertThat(ts_node_parent(child4), Equals(array_node));
AssertThat(ts_node_parent(child5), Equals(array_node));
AssertThat(ts_node_parent(child6), Equals(array_node));
AssertThat(ts_node_parent(child7), Equals(array_node));
AssertThat(ts_node_parent(array_node).data, Equals<void *>(nullptr));
});
});
describe("next_sibling(), prev_sibling()", [&]() {
it("returns the node's next and previous sibling, including anonymous nodes", [&]() {
TSNode bracket_node1 = ts_node_child(array_node, 0);
TSNode number_node = ts_node_child(array_node, 1);
TSNode array_comma_node1 = ts_node_child(array_node, 2);
TSNode false_node = ts_node_child(array_node, 3);
TSNode array_comma_node2 = ts_node_child(array_node, 4);
TSNode object_node = ts_node_child(array_node, 5);
TSNode brace_node1 = ts_node_child(object_node, 0);
TSNode pair_node = ts_node_child(object_node, 1);
TSNode string_node = ts_node_child(pair_node, 0);
TSNode colon_node = ts_node_child(pair_node, 1);
TSNode null_node = ts_node_child(pair_node, 2);
TSNode brace_node2 = ts_node_child(object_node, 2);
TSNode bracket_node2 = ts_node_child(array_node, 6);
AssertThat(ts_node_next_sibling(bracket_node1), Equals(number_node));
AssertThat(ts_node_next_sibling(number_node), Equals(array_comma_node1));
AssertThat(ts_node_next_sibling(array_comma_node1), Equals(false_node));
AssertThat(ts_node_next_sibling(false_node), Equals(array_comma_node2));
AssertThat(ts_node_next_sibling(array_comma_node2), Equals(object_node));
AssertThat(ts_node_next_sibling(object_node), Equals(bracket_node2));
AssertThat(ts_node_next_sibling(bracket_node2).data, Equals<void *>(nullptr));
AssertThat(ts_node_prev_sibling(bracket_node1).data, Equals<void *>(nullptr));
AssertThat(ts_node_prev_sibling(number_node), Equals(bracket_node1));
AssertThat(ts_node_prev_sibling(array_comma_node1), Equals(number_node));
AssertThat(ts_node_prev_sibling(false_node), Equals(array_comma_node1));
AssertThat(ts_node_prev_sibling(array_comma_node2), Equals(false_node));
AssertThat(ts_node_prev_sibling(object_node), Equals(array_comma_node2));
AssertThat(ts_node_prev_sibling(bracket_node2), Equals(object_node));
AssertThat(ts_node_next_sibling(brace_node1), Equals(pair_node));
AssertThat(ts_node_next_sibling(pair_node), Equals(brace_node2));
AssertThat(ts_node_next_sibling(brace_node2).data, Equals<void *>(nullptr));
AssertThat(ts_node_prev_sibling(brace_node1).data, Equals<void *>(nullptr));
AssertThat(ts_node_prev_sibling(pair_node), Equals(brace_node1));
AssertThat(ts_node_prev_sibling(brace_node2), Equals(pair_node));
AssertThat(ts_node_next_sibling(string_node), Equals(colon_node));
AssertThat(ts_node_next_sibling(colon_node), Equals(null_node));
AssertThat(ts_node_next_sibling(null_node).data, Equals<void *>(nullptr));
AssertThat(ts_node_prev_sibling(string_node).data, Equals<void *>(nullptr));
AssertThat(ts_node_prev_sibling(colon_node), Equals(string_node));
AssertThat(ts_node_prev_sibling(null_node), Equals(colon_node));
});
it("returns null when the node has no parent", [&]() {
AssertThat(ts_node_next_named_sibling(array_node).data, Equals<void *>(nullptr));
AssertThat(ts_node_prev_named_sibling(array_node).data, Equals<void *>(nullptr));
});
});
describe("next_named_sibling(), prev_named_sibling()", [&]() {
it("returns the node's next and previous siblings", [&]() {
TSNode number_node = ts_node_named_child(array_node, 0);
TSNode false_node = ts_node_named_child(array_node, 1);
TSNode object_node = ts_node_named_child(array_node, 2);
TSNode pair_node = ts_node_named_child(object_node, 0);
TSNode string_node = ts_node_named_child(pair_node, 0);
TSNode null_node = ts_node_named_child(pair_node, 1);
AssertThat(ts_node_next_named_sibling(number_node), Equals(false_node));
AssertThat(ts_node_next_named_sibling(false_node), Equals(object_node));
AssertThat(ts_node_next_named_sibling(string_node), Equals(null_node));
AssertThat(ts_node_prev_named_sibling(object_node), Equals(false_node));
AssertThat(ts_node_prev_named_sibling(false_node), Equals(number_node));
AssertThat(ts_node_prev_named_sibling(null_node), Equals(string_node));
});
it("returns null when the node has no parent", [&]() {
AssertThat(ts_node_next_named_sibling(array_node).data, Equals<void *>(nullptr));
AssertThat(ts_node_prev_named_sibling(array_node).data, Equals<void *>(nullptr));
});
});
describe("named_descendant_for_char_range(start, end)", [&]() {
describe("when there is a leaf node that spans the given range exactly", [&]() {
it("returns that leaf node", [&]() {
TSNode leaf = ts_node_named_descendant_for_char_range(array_node, string_index, string_end_index - 1);
AssertThat(ts_node_type(leaf, document), Equals("string"));
AssertThat(ts_node_start_byte(leaf), Equals(string_index));
AssertThat(ts_node_end_byte(leaf), Equals(string_end_index));
AssertThat(ts_node_start_point(leaf), Equals<TSPoint>({ 6, 4 }));
AssertThat(ts_node_end_point(leaf), Equals<TSPoint>({ 6, 7 }));
leaf = ts_node_named_descendant_for_char_range(array_node, number_index, number_end_index - 1);
AssertThat(ts_node_type(leaf, document), Equals("number"));
AssertThat(ts_node_start_byte(leaf), Equals(number_index));
AssertThat(ts_node_end_byte(leaf), Equals(number_end_index));
AssertThat(ts_node_start_point(leaf), Equals<TSPoint>({ 3, 2 }));
AssertThat(ts_node_end_point(leaf), Equals<TSPoint>({ 3, 5 }));
});
});
describe("when there is a leaf node that extends beyond the given range", [&]() {
it("returns that leaf node", [&]() {
TSNode leaf = ts_node_named_descendant_for_char_range(array_node, string_index, string_index + 1);
AssertThat(ts_node_type(leaf, document), Equals("string"));
AssertThat(ts_node_start_byte(leaf), Equals(string_index));
AssertThat(ts_node_end_byte(leaf), Equals(string_end_index));
AssertThat(ts_node_start_point(leaf), Equals<TSPoint>({ 6, 4 }));
AssertThat(ts_node_end_point(leaf), Equals<TSPoint>({ 6, 7 }));
leaf = ts_node_named_descendant_for_char_range(array_node, string_index + 1, string_index + 2);
AssertThat(ts_node_type(leaf, document), Equals("string"));
AssertThat(ts_node_start_byte(leaf), Equals(string_index));
AssertThat(ts_node_end_byte(leaf), Equals(string_end_index));
AssertThat(ts_node_start_point(leaf), Equals<TSPoint>({ 6, 4 }));
AssertThat(ts_node_end_point(leaf), Equals<TSPoint>({ 6, 7 }));
});
});
describe("when there is no leaf node that spans the given range", [&]() {
it("returns the smallest node that does span the range", [&]() {
TSNode pair_node = ts_node_named_descendant_for_char_range(array_node, string_index, string_index + 3);
AssertThat(ts_node_type(pair_node, document), Equals("pair"));
AssertThat(ts_node_start_byte(pair_node), Equals(string_index));
AssertThat(ts_node_end_byte(pair_node), Equals(null_end_index));
AssertThat(ts_node_start_point(pair_node), Equals<TSPoint>({ 6, 4 }));
AssertThat(ts_node_end_point(pair_node), Equals<TSPoint>({ 6, 13 }));
});
it("does not return invisible nodes (repeats)", [&]() {
TSNode node = ts_node_named_descendant_for_char_range(array_node, number_end_index, number_end_index + 1);
AssertThat(ts_node_type(node, document), Equals("array"));
AssertThat(ts_node_start_byte(node), Equals(array_index));
AssertThat(ts_node_end_byte(node), Equals(array_end_index));
AssertThat(ts_node_start_point(node), Equals<TSPoint>({ 2, 0 }));
AssertThat(ts_node_end_point(node), Equals<TSPoint>({ 8, 1 }));
});
});
});
describe("descendant_for_char_range(start, end)", [&]() {
it("returns the smallest node that spans the given range", [&]() {
TSNode node1 = ts_node_descendant_for_char_range(array_node, colon_index, colon_index);
AssertThat(ts_node_type(node1, document), Equals(":"));
AssertThat(ts_node_start_byte(node1), Equals(colon_index));
AssertThat(ts_node_end_byte(node1), Equals(colon_index + 1));
AssertThat(ts_node_start_point(node1), Equals<TSPoint>({ 6, 7 }));
AssertThat(ts_node_end_point(node1), Equals<TSPoint>({ 6, 8 }));
TSNode node2 = ts_node_descendant_for_char_range(array_node, string_index + 2, string_index + 4);
AssertThat(ts_node_type(node2, document), Equals("pair"));
AssertThat(ts_node_start_byte(node2), Equals(string_index));
AssertThat(ts_node_end_byte(node2), Equals(null_end_index));
AssertThat(ts_node_start_point(node2), Equals<TSPoint>({ 6, 4 }));
AssertThat(ts_node_end_point(node2), Equals<TSPoint>({ 6, 13 }));
});
});
describe("descendant_for_byte_range(start, end)", [&]() {
it("returns the smallest concrete node that spans the given range", [&]() {
ts_document_set_input_string(document, "[\"αβγδ\", \"αβγδ\"]");
ts_document_parse(document);
TSNode array_node = ts_document_root_node(document);
TSNode node1 = ts_node_descendant_for_char_range(array_node, 7, 7);
AssertThat(ts_node_type(node1, document), Equals(","));
TSNode node2 = ts_node_descendant_for_byte_range(array_node, 6, 10);
AssertThat(ts_node_type(node2, document), Equals("string"));
AssertThat(ts_node_start_byte(node2), Equals<size_t>(1));
AssertThat(ts_node_end_byte(node2), Equals<size_t>(11));
});
});
describe("descendant_for_point_range(start, end)", [&]() {
it("returns the smallest concrete node that spans the given range", [&]() {
TSNode node1 = ts_node_descendant_for_point_range(array_node, {6, 7}, {6, 7});
AssertThat(ts_node_type(node1, document), Equals(":"));
AssertThat(ts_node_start_byte(node1), Equals(colon_index));
AssertThat(ts_node_end_byte(node1), Equals(colon_index + 1));
AssertThat(ts_node_start_point(node1), Equals<TSPoint>({ 6, 7 }));
AssertThat(ts_node_end_point(node1), Equals<TSPoint>({ 6, 8 }));
TSNode node2 = ts_node_descendant_for_point_range(array_node, {6, 6}, {6, 8});
AssertThat(ts_node_type(node2, document), Equals("pair"));
AssertThat(ts_node_start_byte(node2), Equals(string_index));
AssertThat(ts_node_end_byte(node2), Equals(null_end_index));
AssertThat(ts_node_start_point(node2), Equals<TSPoint>({ 6, 4 }));
AssertThat(ts_node_end_point(node2), Equals<TSPoint>({ 6, 13 }));
});
});
});
END_TEST

479
test/runtime/parser_test.cc Normal file
View file

@ -0,0 +1,479 @@
#include "test_helper.h"
#include "runtime/alloc.h"
#include "helpers/record_alloc.h"
#include "helpers/spy_input.h"
#include "helpers/load_language.h"
#include "helpers/record_alloc.h"
#include "helpers/point_helpers.h"
#include "helpers/stderr_logger.h"
#include "helpers/dedent.h"
START_TEST
describe("Parser", [&]() {
TSDocument *document;
SpyInput *input;
TSNode root;
size_t chunk_size;
before_each([&]() {
record_alloc::start();
chunk_size = 3;
input = nullptr;
document = ts_document_new();
});
after_each([&]() {
if (document) ts_document_free(document);
if (input) delete input;
record_alloc::stop();
AssertThat(record_alloc::outstanding_allocation_indices(), IsEmpty());
});
auto set_text = [&](string text) {
input = new SpyInput(text, chunk_size);
ts_document_set_input(document, input->input());
ts_document_parse(document);
root = ts_document_root_node(document);
AssertThat(ts_node_end_byte(root), Equals(text.size()));
input->clear();
};
auto replace_text = [&](size_t position, size_t length, string new_text) {
size_t prev_size = ts_node_end_byte(root);
ts_document_edit(document, input->replace(position, length, new_text));
ts_document_parse(document);
root = ts_document_root_node(document);
size_t new_size = ts_node_end_byte(root);
AssertThat(new_size, Equals(prev_size - length + new_text.size()));
};
auto insert_text = [&](size_t position, string text) {
replace_text(position, 0, text);
};
auto delete_text = [&](size_t position, size_t length) {
replace_text(position, length, "");
};
auto undo = [&]() {
ts_document_edit(document, input->undo());
ts_document_parse(document);
};
auto assert_root_node = [&](const string &expected) {
TSNode node = ts_document_root_node(document);
char *node_string = ts_node_string(node, document);
string actual(node_string);
ts_free(node_string);
AssertThat(actual, Equals(expected));
};
auto get_node_text = [&](TSNode node) {
size_t start = ts_node_start_byte(node);
size_t end = ts_node_end_byte(node);
return input->content.substr(start, end - start);
};
describe("handling errors", [&]() {
describe("when there is an invalid substring right before a valid token", [&]() {
it("computes the error node's size and position correctly", [&]() {
ts_document_set_language(document, load_real_language("json"));
set_text(" [123, @@@@@, true]");
assert_root_node(
"(array (number) (ERROR (UNEXPECTED '@')) (true))");
TSNode error = ts_node_named_child(root, 1);
AssertThat(ts_node_type(error, document), Equals("ERROR"));
AssertThat(get_node_text(error), Equals(", @@@@@"));
AssertThat(ts_node_child_count(error), Equals<size_t>(2));
TSNode comma = ts_node_child(error, 0);
AssertThat(get_node_text(comma), Equals(","));
TSNode garbage = ts_node_child(error, 1);
AssertThat(get_node_text(garbage), Equals("@@@@@"));
TSNode node_after_error = ts_node_named_child(root, 2);
AssertThat(ts_node_type(node_after_error, document), Equals("true"));
AssertThat(get_node_text(node_after_error), Equals("true"));
});
});
describe("when there is an unexpected string in the middle of a token", [&]() {
it("computes the error node's size and position correctly", [&]() {
ts_document_set_language(document, load_real_language("json"));
set_text(" [123, faaaaalse, true]");
assert_root_node(
"(array (number) (ERROR (UNEXPECTED 'a')) (true))");
TSNode error = ts_node_named_child(root, 1);
AssertThat(ts_node_type(error, document), Equals("ERROR"));
AssertThat(ts_node_child_count(error), Equals<size_t>(2));
TSNode comma = ts_node_child(error, 0);
AssertThat(ts_node_type(comma, document), Equals(","));
AssertThat(get_node_text(comma), Equals(","));
TSNode garbage = ts_node_child(error, 1);
AssertThat(ts_node_type(garbage, document), Equals("ERROR"));
AssertThat(get_node_text(garbage), Equals("faaaaalse"));
TSNode last = ts_node_named_child(root, 2);
AssertThat(ts_node_type(last, document), Equals("true"));
AssertThat(ts_node_start_byte(last), Equals(strlen(" [123, faaaaalse, ")));
});
});
describe("when there is one unexpected token between two valid tokens", [&]() {
it("computes the error node's size and position correctly", [&]() {
ts_document_set_language(document, load_real_language("json"));
set_text(" [123, true false, true]");
assert_root_node(
"(array (number) (true) (ERROR (false)) (true))");
TSNode error = ts_node_named_child(root, 2);
AssertThat(ts_node_type(error, document), Equals("ERROR"));
AssertThat(get_node_text(error), Equals("false"));
AssertThat(ts_node_child_count(error), Equals<size_t>(1));
TSNode last = ts_node_named_child(root, 1);
AssertThat(ts_node_type(last, document), Equals("true"));
AssertThat(get_node_text(last), Equals("true"));
});
});
describe("when there is an unexpected string at the end of a token", [&]() {
it("computes the error's size and position correctly", [&]() {
ts_document_set_language(document, load_real_language("json"));
set_text(" [123, \"hi\n, true]");
assert_root_node(
"(array (number) (ERROR (UNEXPECTED '\\n')) (true))");
});
});
describe("when there is an unterminated error", [&]() {
it("maintains a consistent tree", [&]() {
ts_document_set_language(document, load_real_language("javascript"));
set_text("a; /* b");
assert_root_node(
"(ERROR (program (expression_statement (identifier))) (UNEXPECTED EOF))");
});
});
describe("when there are extra tokens at the end of the viable prefix", [&]() {
it("does not include them in the error node", [&]() {
ts_document_set_language(document, load_real_language("javascript"));
set_text(
"var x;\n"
"\n"
"if\n"
"\n"
"var y;"
);
TSNode error = ts_node_named_child(root, 1);
AssertThat(ts_node_type(error, document), Equals("ERROR"));
AssertThat(ts_node_start_point(error), Equals<TSPoint>({2, 0}));
AssertThat(ts_node_end_point(error), Equals<TSPoint>({2, 2}));
});
});
});
describe("handling extra tokens", [&]() {
describe("when the token appears as part of a grammar rule", [&]() {
it("incorporates it into the tree", [&]() {
ts_document_set_language(document, load_real_language("javascript"));
set_text("fn()\n");
assert_root_node(
"(program (expression_statement (function_call (identifier) (arguments))))");
});
});
describe("when the token appears somewhere else", [&]() {
it("incorporates it into the tree", [&]() {
ts_document_set_language(document, load_real_language("javascript"));
set_text(
"fn()\n"
" .otherFn();");
assert_root_node(
"(program (expression_statement (function_call "
"(member_access "
"(function_call (identifier) (arguments)) "
"(identifier)) "
"(arguments))))");
});
});
describe("when several extra tokens appear in a row", [&]() {
it("incorporates them into the tree", [&]() {
ts_document_set_language(document, load_real_language("javascript"));
set_text(
"fn()\n\n"
"// This is a comment"
"\n\n"
".otherFn();");
assert_root_node(
"(program (expression_statement (function_call "
"(member_access "
"(function_call (identifier) (arguments)) "
"(comment) "
"(identifier)) "
"(arguments))))");
});
});
});
describe("editing", [&]() {
describe("creating new tokens near the end of the input", [&]() {
it("updates the parse tree and re-reads only the changed portion of the text", [&]() {
ts_document_set_language(document, load_real_language("javascript"));
set_text("x * (100 + abc);");
assert_root_node(
"(program (expression_statement (math_op "
"(identifier) "
"(math_op (number) (identifier)))))");
insert_text(strlen("x * (100 + abc"), ".d");
assert_root_node(
"(program (expression_statement (math_op "
"(identifier) "
"(math_op (number) (member_access (identifier) (identifier))))))");
AssertThat(input->strings_read, Equals(vector<string>({ " + abc.d)" })));
});
});
describe("creating new tokens near the beginning of the input", [&]() {
it("updates the parse tree and re-reads only the changed portion of the input", [&]() {
chunk_size = 2;
ts_document_set_language(document, load_real_language("javascript"));
set_text("123 + 456 * (10 + x);");
assert_root_node(
"(program (expression_statement (math_op "
"(number) "
"(math_op (number) (math_op (number) (identifier))))))");
insert_text(strlen("123"), " || 5");
assert_root_node(
"(program (expression_statement (bool_op "
"(number) "
"(math_op "
"(number) "
"(math_op (number) (math_op (number) (identifier)))))))");
AssertThat(input->strings_read, Equals(vector<string>({ "123 || 5 +" })));
});
});
describe("introducing an error", [&]() {
it("gives the error the right size", [&]() {
ts_document_set_language(document, load_real_language("javascript"));
set_text("var x = y;");
assert_root_node(
"(program (var_declaration (var_assignment "
"(identifier) (identifier))))");
insert_text(strlen("var x = y"), " *");
assert_root_node(
"(program (var_declaration (var_assignment "
"(identifier) (identifier)) (ERROR)))");
insert_text(strlen("var x = y *"), " z");
assert_root_node(
"(program (var_declaration (var_assignment "
"(identifier) (math_op (identifier) (identifier)))))");
});
});
describe("into the middle of an existing token", [&]() {
it("updates the parse tree", [&]() {
ts_document_set_language(document, load_real_language("javascript"));
set_text("abc * 123;");
assert_root_node(
"(program (expression_statement (math_op (identifier) (number))))");
insert_text(strlen("ab"), "XYZ");
assert_root_node(
"(program (expression_statement (math_op (identifier) (number))))");
TSNode node = ts_node_named_descendant_for_char_range(root, 1, 1);
AssertThat(ts_node_type(node, document), Equals("identifier"));
AssertThat(ts_node_end_byte(node), Equals(strlen("abXYZc")));
});
});
describe("at the end of an existing token", [&]() {
it("updates the parse tree", [&]() {
ts_document_set_language(document, load_real_language("javascript"));
set_text("abc * 123;");
assert_root_node(
"(program (expression_statement (math_op (identifier) (number))))");
insert_text(strlen("abc"), "XYZ");
assert_root_node(
"(program (expression_statement (math_op (identifier) (number))))");
TSNode node = ts_node_named_descendant_for_char_range(root, 1, 1);
AssertThat(ts_node_type(node, document), Equals("identifier"));
AssertThat(ts_node_end_byte(node), Equals(strlen("abcXYZ")));
});
});
describe("inserting text into a node containing a extra token", [&]() {
it("updates the parse tree", [&]() {
ts_document_set_language(document, load_real_language("javascript"));
set_text("123 *\n"
"// a-comment\n"
"abc;");
assert_root_node(
"(program (expression_statement (math_op "
"(number) "
"(comment) "
"(identifier))))");
insert_text(
strlen("123 *\n"
"// a-comment\n"
"abc"),
"XYZ");
assert_root_node(
"(program (expression_statement (math_op "
"(number) "
"(comment) "
"(identifier))))");
});
});
describe("when a critical token is removed", [&]() {
it("updates the parse tree, creating an error", [&]() {
ts_document_set_language(document, load_real_language("javascript"));
set_text("123 * 456; 789 * 123;");
assert_root_node(
"(program "
"(expression_statement (math_op (number) (number))) "
"(expression_statement (math_op (number) (number))))");
delete_text(strlen("123 "), 2);
assert_root_node(
"(program "
"(expression_statement (number) (ERROR (number))) "
"(expression_statement (math_op (number) (number))))");
});
});
describe("with external tokens", [&]() {
it("maintains the external scanner's state during incremental parsing", [&]() {
ts_document_set_language(document, load_real_language("python"));
string text = dedent(R"PYTHON(
if a:
print b
return c
)PYTHON");
set_text(text);
assert_root_node("(module "
"(if_statement (identifier) "
"(print_statement (identifier))) "
"(return_statement (expression_list (identifier))))");
replace_text(text.find("return"), 0, " ");
assert_root_node("(module "
"(if_statement (identifier) "
"(print_statement (identifier)) "
"(return_statement (expression_list (identifier)))))");
undo();
assert_root_node("(module "
"(if_statement (identifier) "
"(print_statement (identifier))) "
"(return_statement (expression_list (identifier))))");
});
});
it("does not try to re-use nodes that are within the edited region", [&]() {
ts_document_set_language(document, load_real_language("javascript"));
set_text("{ x: (b.c) };");
assert_root_node(
"(program (expression_statement (object (pair "
"(identifier) (member_access (identifier) (identifier))))))");
replace_text(strlen("{ x: "), strlen("(b.c)"), "b.c");
assert_root_node(
"(program (expression_statement (object (pair "
"(identifier) (member_access (identifier) (identifier))))))");
});
it("updates the document's parse count", [&]() {
ts_document_set_language(document, load_real_language("javascript"));
AssertThat(ts_document_parse_count(document), Equals<size_t>(0));
set_text("{ x: (b.c) };");
AssertThat(ts_document_parse_count(document), Equals<size_t>(1));
insert_text(strlen("{ x"), "yz");
AssertThat(ts_document_parse_count(document), Equals<size_t>(2));
});
});
describe("lexing", [&]() {
describe("handling tokens containing wildcard patterns (e.g. comments)", [&]() {
it("terminates them at the end of the document", [&]() {
ts_document_set_language(document, load_real_language("javascript"));
set_text("x; // this is a comment");
assert_root_node(
"(program (expression_statement (identifier)) (comment))");
TSNode comment = ts_node_named_child(root, 1);
AssertThat(ts_node_start_byte(comment), Equals(strlen("x; ")));
AssertThat(ts_node_end_byte(comment), Equals(strlen("x; // this is a comment")));
});
});
it("recognizes UTF8 characters as single characters", [&]() {
// 'ΩΩΩ — ΔΔ';
ts_document_set_language(document, load_real_language("javascript"));
set_text("'\u03A9\u03A9\u03A9 \u2014 \u0394\u0394';");
assert_root_node(
"(program (expression_statement (string)))");
AssertThat(ts_node_end_char(root), Equals(strlen("'OOO - DD';")));
AssertThat(ts_node_end_byte(root), Equals(strlen("'\u03A9\u03A9\u03A9 \u2014 \u0394\u0394';")));
});
});
});
END_TEST

571
test/runtime/stack_test.cc Normal file
View file

@ -0,0 +1,571 @@
#include "test_helper.h"
#include "helpers/tree_helpers.h"
#include "helpers/point_helpers.h"
#include "helpers/record_alloc.h"
#include "helpers/stream_methods.h"
#include "runtime/stack.h"
#include "runtime/tree.h"
#include "runtime/length.h"
#include "runtime/alloc.h"
enum {
stateA = 2,
stateB,
stateC, stateD, stateE, stateF, stateG, stateH, stateI, stateJ
};
enum {
symbol0, symbol1, symbol2, symbol3, symbol4, symbol5, symbol6, symbol7, symbol8,
symbol9, symbol10
};
Length operator*(const Length &length, uint32_t factor) {
return {length.bytes * factor, length.chars * factor, {0, length.extent.column * factor}};
}
void free_slice_array(StackSliceArray *slices) {
for (size_t i = 0; i < slices->size; i++) {
StackSlice slice = slices->contents[i];
bool matches_prior_trees = false;
for (size_t j = 0; j < i; j++) {
StackSlice prior_slice = slices->contents[j];
if (slice.trees.contents == prior_slice.trees.contents) {
matches_prior_trees = true;
break;
}
}
if (!matches_prior_trees) {
for (size_t j = 0; j < slice.trees.size; j++)
ts_tree_release(slice.trees.contents[j]);
array_delete(&slice.trees);
}
}
}
struct StackEntry {
TSStateId state;
size_t depth;
};
vector<StackEntry> get_stack_entries(Stack *stack, StackVersion version) {
vector<StackEntry> result;
ts_stack_iterate(
stack,
version,
[](void *payload, TSStateId state, TreeArray *trees, uint32_t tree_count, bool is_done, bool is_pending) -> StackIterateAction {
auto entries = static_cast<vector<StackEntry> *>(payload);
StackEntry entry = {state, tree_count};
if (find(entries->begin(), entries->end(), entry) == entries->end())
entries->push_back(entry);
return StackIterateNone;
}, &result);
return result;
}
START_TEST
describe("Stack", [&]() {
Stack *stack;
const size_t tree_count = 11;
Tree *trees[tree_count];
Length tree_len = {2, 3, {0, 3}};
before_each([&]() {
record_alloc::start();
stack = ts_stack_new();
for (size_t i = 0; i < tree_count; i++)
trees[i] = ts_tree_make_leaf(i, length_zero(), tree_len, {
true, true, false, true,
});
});
after_each([&]() {
ts_stack_delete(stack);
for (size_t i = 0; i < tree_count; i++)
ts_tree_release(trees[i]);
record_alloc::stop();
AssertThat(record_alloc::outstanding_allocation_indices(), IsEmpty());
});
describe("push(version, tree, is_pending, state)", [&]() {
it("adds entries to the given version of the stack", [&]() {
AssertThat(ts_stack_version_count(stack), Equals<size_t>(1));
AssertThat(ts_stack_top_state(stack, 0), Equals(1));
AssertThat(ts_stack_top_position(stack, 0), Equals(length_zero()));
// . <──0── A*
ts_stack_push(stack, 0, trees[0], false, stateA);
AssertThat(ts_stack_top_state(stack, 0), Equals(stateA));
AssertThat(ts_stack_top_position(stack, 0), Equals(tree_len));
// . <──0── A <──1── B*
ts_stack_push(stack, 0, trees[1], false, stateB);
AssertThat(ts_stack_top_state(stack, 0), Equals(stateB));
AssertThat(ts_stack_top_position(stack, 0), Equals(tree_len * 2));
// . <──0── A <──1── B <──2── C*
ts_stack_push(stack, 0, trees[2], false, stateC);
AssertThat(ts_stack_top_state(stack, 0), Equals(stateC));
AssertThat(ts_stack_top_position(stack, 0), Equals(tree_len * 3));
AssertThat(get_stack_entries(stack, 0), Equals(vector<StackEntry>({
{stateC, 0},
{stateB, 1},
{stateA, 2},
{1, 3},
})));
});
it("increments the version's push count", [&]() {
AssertThat(ts_stack_push_count(stack, 0), Equals<unsigned>(0));
ts_stack_push(stack, 0, trees[0], false, stateA);
AssertThat(ts_stack_push_count(stack, 0), Equals<unsigned>(1));
});
});
describe("merge()", [&]() {
before_each([&]() {
// . <──0── A <──1── B*
// ↑
// └───2─── C*
ts_stack_push(stack, 0, trees[0], false, stateA);
ts_stack_copy_version(stack, 0);
ts_stack_push(stack, 0, trees[1], false, stateB);
ts_stack_push(stack, 1, trees[2], false, stateC);
});
it("combines versions that have the same top states and positions", [&]() {
// . <──0── A <──1── B <──3── D*
// ↑
// └───2─── C <──4── D*
ts_stack_push(stack, 0, trees[3], false, stateD);
ts_stack_push(stack, 1, trees[4], false, stateD);
// . <──0── A <──1── B <──3── D*
// ↑ |
// └───2─── C <──4───┘
AssertThat(ts_stack_merge(stack, 0, 1), IsTrue());
AssertThat(ts_stack_version_count(stack), Equals<size_t>(1));
AssertThat(get_stack_entries(stack, 0), Equals(vector<StackEntry>({
{stateD, 0},
{stateB, 1},
{stateC, 1},
{stateA, 2},
{1, 3},
})));
});
it("does not combine versions that have different states", [&]() {
AssertThat(ts_stack_merge(stack, 0, 1), IsFalse());
AssertThat(ts_stack_version_count(stack), Equals<size_t>(2));
});
it("does not combine versions that have different positions", [&]() {
// . <──0── A <──1── B <────3──── D*
// ↑
// └───2─── C <──4── D*
trees[3]->size = tree_len * 3;
ts_stack_push(stack, 0, trees[3], false, stateD);
ts_stack_push(stack, 1, trees[4], false, stateD);
AssertThat(ts_stack_merge(stack, 0, 1), IsFalse());
AssertThat(ts_stack_version_count(stack), Equals<size_t>(2));
});
describe("when the merged versions have more than one common entry", [&]() {
it("combines all of the top common entries", [&]() {
// . <──0── A <──1── B <──3── D <──5── E*
// ↑
// └───2─── C <──4── D <──5── E*
ts_stack_push(stack, 0, trees[3], false, stateD);
ts_stack_push(stack, 0, trees[5], false, stateE);
ts_stack_push(stack, 1, trees[4], false, stateD);
ts_stack_push(stack, 1, trees[5], false, stateE);
// . <──0── A <──1── B <──3── D <──5── E*
// ↑ |
// └───2─── C <──4───┘
AssertThat(ts_stack_merge(stack, 0, 1), IsTrue());
AssertThat(ts_stack_version_count(stack), Equals<size_t>(1));
AssertThat(get_stack_entries(stack, 0), Equals(vector<StackEntry>({
{stateE, 0},
{stateD, 1},
{stateB, 2},
{stateC, 2},
{stateA, 3},
{1, 4},
})));
});
});
});
describe("pop_count(version, count)", [&]() {
before_each([&]() {
// . <──0── A <──1── B <──2── C*
ts_stack_push(stack, 0, trees[0], false, stateA);
ts_stack_push(stack, 0, trees[1], false, stateB);
ts_stack_push(stack, 0, trees[2], false, stateC);
});
it("creates a new version with the given number of entries removed", [&]() {
// . <──0── A <──1── B <──2── C*
// ↑
// └─*
StackPopResult pop = ts_stack_pop_count(stack, 0, 2);
AssertThat(pop.stopped_at_error, Equals(false));
AssertThat(pop.slices.size, Equals<size_t>(1));
AssertThat(ts_stack_version_count(stack), Equals<size_t>(2));
StackSlice slice = pop.slices.contents[0];
AssertThat(slice.version, Equals<StackVersion>(1));
AssertThat(slice.trees, Equals(vector<Tree *>({ trees[1], trees[2] })));
AssertThat(ts_stack_top_state(stack, 1), Equals(stateA));
free_slice_array(&pop.slices);
});
it("does not count 'extra' trees toward the given count", [&]() {
trees[1]->extra = true;
// . <──0── A <──1── B <──2── C*
// ↑
// └─*
StackPopResult pop = ts_stack_pop_count(stack, 0, 2);
AssertThat(pop.stopped_at_error, Equals(false));
AssertThat(pop.slices.size, Equals<size_t>(1));
StackSlice slice = pop.slices.contents[0];
AssertThat(slice.trees, Equals(vector<Tree *>({ trees[0], trees[1], trees[2] })));
AssertThat(ts_stack_top_state(stack, 1), Equals(1));
free_slice_array(&pop.slices);
});
it("stops popping entries early if it reaches an error tree", [&]() {
// . <──0── A <──1── B <──2── C <──3── ERROR <──4── D*
ts_stack_push(stack, 0, trees[3], false, ERROR_STATE);
ts_stack_push(stack, 0, trees[4], false, stateD);
// . <──0── A <──1── B <──2── C <──3── ERROR <──4── D*
// ↑
// └─*
StackPopResult pop = ts_stack_pop_count(stack, 0, 3);
AssertThat(pop.stopped_at_error, Equals(true));
AssertThat(ts_stack_version_count(stack), Equals<size_t>(2));
AssertThat(ts_stack_top_state(stack, 1), Equals(ERROR_STATE));
AssertThat(pop.slices.size, Equals<size_t>(1));
StackSlice slice = pop.slices.contents[0];
AssertThat(slice.version, Equals<StackVersion>(1));
AssertThat(slice.trees, Equals(vector<Tree *>({ trees[4] })));
free_slice_array(&pop.slices);
});
it("preserves the push count of the popped version", [&]() {
// . <──0── A <──1── B <──2── C*
// ↑
// └─*
StackPopResult pop = ts_stack_pop_count(stack, 0, 2);
AssertThat(ts_stack_push_count(stack, 0), Equals<unsigned>(3));
AssertThat(ts_stack_push_count(stack, 1), Equals<unsigned>(3));
free_slice_array(&pop.slices);
});
describe("when the version has been merged", [&]() {
before_each([&]() {
// . <──0── A <──1── B <──2── C <──3── D <──10── I*
// ↑ |
// └───4─── E <──5── F <──6───┘
ts_stack_push(stack, 0, trees[3], false, stateD);
StackPopResult pop = ts_stack_pop_count(stack, 0, 3);
free_slice_array(&pop.slices);
ts_stack_push(stack, 1, trees[4], false, stateE);
ts_stack_push(stack, 1, trees[5], false, stateF);
ts_stack_push(stack, 1, trees[6], false, stateD);
ts_stack_merge(stack, 0, 1);
ts_stack_push(stack, 0, trees[10], false, stateI);
AssertThat(ts_stack_version_count(stack), Equals<size_t>(1));
AssertThat(get_stack_entries(stack, 0), Equals(vector<StackEntry>({
{stateI, 0},
{stateD, 1},
{stateC, 2},
{stateF, 2},
{stateB, 3},
{stateE, 3},
{stateA, 4},
{1, 5},
})));
});
describe("when there are two paths that reveal different versions", [&]() {
it("returns an entry for each revealed version", [&]() {
// . <──0── A <──1── B <──2── C <──3── D <──10── I*
// ↑ ↑
// | └*
// |
// └───4─── E*
StackPopResult pop = ts_stack_pop_count(stack, 0, 3);
AssertThat(pop.slices.size, Equals<size_t>(2));
StackSlice slice1 = pop.slices.contents[0];
AssertThat(slice1.version, Equals<StackVersion>(1));
AssertThat(slice1.trees, Equals(vector<Tree *>({ trees[2], trees[3], trees[10] })));
StackSlice slice2 = pop.slices.contents[1];
AssertThat(slice2.version, Equals<StackVersion>(2));
AssertThat(slice2.trees, Equals(vector<Tree *>({ trees[5], trees[6], trees[10] })));
AssertThat(ts_stack_version_count(stack), Equals<size_t>(3));
AssertThat(get_stack_entries(stack, 0), Equals(vector<StackEntry>({
{stateI, 0},
{stateD, 1},
{stateC, 2},
{stateF, 2},
{stateB, 3},
{stateE, 3},
{stateA, 4},
{1, 5},
})));
AssertThat(get_stack_entries(stack, 1), Equals(vector<StackEntry>({
{stateB, 0},
{stateA, 1},
{1, 2},
})));
AssertThat(get_stack_entries(stack, 2), Equals(vector<StackEntry>({
{stateE, 0},
{stateA, 1},
{1, 2},
})));
free_slice_array(&pop.slices);
});
});
describe("when there is one path that ends at a merged version", [&]() {
it("returns a single entry", [&]() {
// . <──0── A <──1── B <──2── C <──3── D <──10── I*
// | |
// └───5─── F <──6── G <──7───┘
// |
// └*
StackPopResult pop = ts_stack_pop_count(stack, 0, 1);
AssertThat(pop.slices.size, Equals<size_t>(1));
StackSlice slice1 = pop.slices.contents[0];
AssertThat(slice1.version, Equals<StackVersion>(1));
AssertThat(slice1.trees, Equals(vector<Tree *>({ trees[10] })));
AssertThat(ts_stack_version_count(stack), Equals<size_t>(2));
AssertThat(ts_stack_top_state(stack, 0), Equals(stateI));
AssertThat(ts_stack_top_state(stack, 1), Equals(stateD));
free_slice_array(&pop.slices);
});
});
describe("when there are two paths that converge on one version", [&]() {
it("returns two slices with the same version", [&]() {
// . <──0── A <──1── B <──2── C <──3── D <──10── I*
// ↑ |
// ├───4─── E <──5── F <──6───┘
// |
// └*
StackPopResult pop = ts_stack_pop_count(stack, 0, 4);
AssertThat(pop.slices.size, Equals<size_t>(2));
StackSlice slice1 = pop.slices.contents[0];
AssertThat(slice1.version, Equals<StackVersion>(1));
AssertThat(slice1.trees, Equals(vector<Tree *>({ trees[1], trees[2], trees[3], trees[10] })));
StackSlice slice2 = pop.slices.contents[1];
AssertThat(slice2.version, Equals<StackVersion>(1));
AssertThat(slice2.trees, Equals(vector<Tree *>({ trees[4], trees[5], trees[6], trees[10] })))
AssertThat(ts_stack_version_count(stack), Equals<size_t>(2));
AssertThat(ts_stack_top_state(stack, 0), Equals(stateI));
AssertThat(ts_stack_top_state(stack, 1), Equals(stateA));
free_slice_array(&pop.slices);
});
});
describe("when there are three paths that lead to three different versions", [&]() {
it("returns three entries with different arrays of trees", [&]() {
// . <──0── A <──1── B <──2── C <──3── D <──10── I*
// ↑ |
// ├───4─── E <──5── F <──6───┘
// | |
// └───7─── G <──8── H <──9───┘
StackPopResult pop = ts_stack_pop_count(stack, 0, 4);
free_slice_array(&pop.slices);
ts_stack_push(stack, 1, trees[7], false, stateG);
ts_stack_push(stack, 1, trees[8], false, stateH);
ts_stack_push(stack, 1, trees[9], false, stateD);
ts_stack_push(stack, 1, trees[10], false, stateI);
ts_stack_merge(stack, 0, 1);
AssertThat(ts_stack_version_count(stack), Equals<size_t>(1));
AssertThat(get_stack_entries(stack, 0), Equals(vector<StackEntry>({
{stateI, 0},
{stateD, 1},
{stateC, 2},
{stateF, 2},
{stateH, 2},
{stateB, 3},
{stateE, 3},
{stateG, 3},
{stateA, 4},
{1, 5},
})));
// . <──0── A <──1── B <──2── C <──3── D <──10── I*
// ↑ ↑
// | └*
// |
// ├───4─── E <──5── F*
// |
// └───7─── G <──8── H*
pop = ts_stack_pop_count(stack, 0, 2);
AssertThat(pop.slices.size, Equals<size_t>(3));
StackSlice slice1 = pop.slices.contents[0];
AssertThat(slice1.version, Equals<StackVersion>(1));
AssertThat(slice1.trees, Equals(vector<Tree *>({ trees[3], trees[10] })))
StackSlice slice2 = pop.slices.contents[1];
AssertThat(slice2.version, Equals<StackVersion>(2));
AssertThat(slice2.trees, Equals(vector<Tree *>({ trees[6], trees[10] })))
StackSlice slice3 = pop.slices.contents[2];
AssertThat(slice3.version, Equals<StackVersion>(3));
AssertThat(slice3.trees, Equals(vector<Tree *>({ trees[9], trees[10] })))
AssertThat(ts_stack_version_count(stack), Equals<size_t>(4));
AssertThat(ts_stack_top_state(stack, 0), Equals(stateI));
AssertThat(ts_stack_top_state(stack, 1), Equals(stateC));
AssertThat(ts_stack_top_state(stack, 2), Equals(stateF));
AssertThat(ts_stack_top_state(stack, 3), Equals(stateH));
free_slice_array(&pop.slices);
});
});
});
});
describe("pop_pending(version)", [&]() {
before_each([&]() {
ts_stack_push(stack, 0, trees[0], false, stateA);
});
it("removes the top node from the stack if it was pushed in pending mode", [&]() {
ts_stack_push(stack, 0, trees[1], true, stateB);
StackPopResult pop = ts_stack_pop_pending(stack, 0);
AssertThat(pop.stopped_at_error, Equals(false));
AssertThat(pop.slices.size, Equals<size_t>(1));
AssertThat(get_stack_entries(stack, 0), Equals(vector<StackEntry>({
{stateA, 0},
{1, 1},
})));
free_slice_array(&pop.slices);
});
it("skips entries whose trees are extra", [&]() {
ts_stack_push(stack, 0, trees[1], true, stateB);
trees[2]->extra = true;
trees[3]->extra = true;
ts_stack_push(stack, 0, trees[2], false, stateB);
ts_stack_push(stack, 0, trees[3], false, stateB);
StackPopResult pop = ts_stack_pop_pending(stack, 0);
AssertThat(pop.stopped_at_error, Equals(false));
AssertThat(pop.slices.size, Equals<size_t>(1));
AssertThat(pop.slices.contents[0].trees, Equals(vector<Tree *>({ trees[1], trees[2], trees[3] })));
AssertThat(get_stack_entries(stack, 0), Equals(vector<StackEntry>({
{stateA, 0},
{1, 1},
})));
free_slice_array(&pop.slices);
});
it("does nothing if the top node was not pushed in pending mode", [&]() {
ts_stack_push(stack, 0, trees[1], false, stateB);
StackPopResult pop = ts_stack_pop_pending(stack, 0);
AssertThat(pop.stopped_at_error, Equals(false));
AssertThat(pop.slices.size, Equals<size_t>(0));
AssertThat(get_stack_entries(stack, 0), Equals(vector<StackEntry>({
{stateB, 0},
{stateA, 1},
{1, 2},
})));
free_slice_array(&pop.slices);
});
});
describe("setting external token state", [&]() {
TSExternalTokenState external_token_state1, external_token_state2;
it("allows the state to be retrieved", [&]() {
AssertThat(ts_stack_external_token_state(stack, 0), Equals(nullptr));
ts_stack_set_external_token_state(stack, 0, &external_token_state1);
AssertThat(ts_stack_external_token_state(stack, 0), Equals(&external_token_state1));
ts_stack_copy_version(stack, 0);
AssertThat(ts_stack_external_token_state(stack, 0), Equals(&external_token_state1));
});
it("does not merge stack versions with different external token states", [&]() {
ts_stack_copy_version(stack, 0);
ts_stack_push(stack, 0, trees[0], false, 5);
ts_stack_push(stack, 1, trees[0], false, 5);
ts_stack_set_external_token_state(stack, 0, &external_token_state1);
ts_stack_set_external_token_state(stack, 0, &external_token_state2);
AssertThat(ts_stack_merge(stack, 0, 1), IsFalse());
});
});
});
END_TEST
bool operator==(const StackEntry &left, const StackEntry &right) {
return left.state == right.state && left.depth == right.depth;
}
std::ostream &operator<<(std::ostream &stream, const StackEntry &entry) {
return stream << "{" << entry.state << ", " << entry.depth << "}";
}
std::ostream &operator<<(std::ostream &stream, const TreeArray &array) {
stream << "[";
bool first = true;
for (size_t i = 0; i < array.size; i++) {
if (!first)
stream << ", ";
first = false;
stream << array.contents[i];
}
return stream << "]";
}

439
test/runtime/tree_test.cc Normal file
View file

@ -0,0 +1,439 @@
#include "test_helper.h"
#include "helpers/tree_helpers.h"
#include "helpers/point_helpers.h"
#include "runtime/tree.h"
#include "runtime/length.h"
void assert_consistent(const Tree *tree) {
if (tree->child_count == 0)
return;
AssertThat(tree->children[0]->padding, Equals<Length>(tree->padding));
Length total_children_size = length_zero();
for (size_t i = 0; i < tree->child_count; i++) {
Tree *child = tree->children[i];
AssertThat(child->context.offset, Equals(total_children_size));
assert_consistent(child);
total_children_size = length_add(total_children_size, ts_tree_total_size(child));
}
AssertThat(total_children_size, Equals<Length>(ts_tree_total_size(tree)));
};
START_TEST
describe("Tree", []() {
enum {
symbol1 = 1,
symbol2,
symbol3,
symbol4,
symbol5,
symbol6,
symbol7,
symbol8,
symbol9,
};
TSSymbolMetadata visible = {true, true, false, true};
TSSymbolMetadata invisible = {false, false, false, true};
describe("make_leaf", [&]() {
it("does not mark the tree as fragile", [&]() {
Tree *tree = ts_tree_make_leaf(symbol1, {2, 1, {0, 1}}, {5, 4, {0, 4}}, visible);
AssertThat(tree->fragile_left, IsFalse());
AssertThat(tree->fragile_right, IsFalse());
});
});
describe("make_error", [&]() {
it("marks the tree as fragile", [&]() {
Tree *error_tree = ts_tree_make_error(
length_zero(),
length_zero(),
'z');
AssertThat(error_tree->fragile_left, IsTrue());
AssertThat(error_tree->fragile_right, IsTrue());
ts_tree_release(error_tree);
});
});
describe("make_node", [&]() {
Tree *tree1, *tree2, *parent1;
before_each([&]() {
tree1 = ts_tree_make_leaf(symbol1, {2, 1, {0, 1}}, {5, 4, {0, 4}}, visible);
tree2 = ts_tree_make_leaf(symbol2, {1, 1, {0, 1}}, {3, 3, {0, 3}}, visible);
ts_tree_retain(tree1);
ts_tree_retain(tree2);
parent1 = ts_tree_make_node(symbol3, 2, tree_array({
tree1,
tree2,
}), visible);
});
after_each([&]() {
ts_tree_release(tree1);
ts_tree_release(tree2);
ts_tree_release(parent1);
});
it("computes its size and padding based on its child nodes", [&]() {
AssertThat(parent1->size.bytes, Equals<size_t>(
tree1->size.bytes + tree2->padding.bytes + tree2->size.bytes));
AssertThat(parent1->size.chars, Equals<size_t>(
tree1->size.chars + tree2->padding.chars + tree2->size.chars));
AssertThat(parent1->padding.bytes, Equals<size_t>(tree1->padding.bytes));
AssertThat(parent1->padding.chars, Equals<size_t>(tree1->padding.chars));
});
describe("when the first node is fragile on the left side", [&]() {
Tree *parent;
before_each([&]() {
tree1->fragile_left = true;
tree1->extra = true;
ts_tree_retain(tree1);
ts_tree_retain(tree2);
parent = ts_tree_make_node(symbol3, 2, tree_array({
tree1,
tree2,
}), visible);
});
after_each([&]() {
ts_tree_release(parent);
});
it("records that it is fragile on the left side", [&]() {
AssertThat(parent->fragile_left, IsTrue());
});
});
describe("when the last node is fragile on the right side", [&]() {
Tree *parent;
before_each([&]() {
tree2->fragile_right = true;
tree2->extra = true;
ts_tree_retain(tree1);
ts_tree_retain(tree2);
parent = ts_tree_make_node(symbol3, 2, tree_array({
tree1,
tree2,
}), visible);
});
after_each([&]() {
ts_tree_release(parent);
});
it("records that it is fragile on the right side", [&]() {
AssertThat(parent->fragile_right, IsTrue());
});
});
describe("when the outer nodes aren't fragile on their outer side", [&]() {
Tree *parent;
before_each([&]() {
tree1->fragile_right = true;
tree2->fragile_left = true;
ts_tree_retain(tree1);
ts_tree_retain(tree2);
parent = ts_tree_make_node(symbol3, 2, tree_array({
tree1,
tree2,
}), visible);
});
after_each([&]() {
ts_tree_release(parent);
});
it("records that it is not fragile", [&]() {
AssertThat(parent->fragile_left, IsFalse());
AssertThat(parent->fragile_right, IsFalse());
});
});
});
describe("edit", [&]() {
Tree *tree = nullptr;
before_each([&]() {
tree = ts_tree_make_node(symbol1, 3, tree_array({
ts_tree_make_leaf(symbol2, {2, 2, {0, 2}}, {3, 3, {0, 3}}, visible),
ts_tree_make_leaf(symbol3, {2, 2, {0, 2}}, {3, 3, {0, 3}}, visible),
ts_tree_make_leaf(symbol4, {2, 2, {0, 2}}, {3, 3, {0, 3}}, visible),
}), visible);
AssertThat(tree->padding, Equals<Length>({2, 2, {0, 2}}));
AssertThat(tree->size, Equals<Length>({13, 13, {0, 13}}));
});
after_each([&]() {
ts_tree_release(tree);
});
describe("edits within a tree's padding", [&]() {
it("resizes the padding of the tree and its leftmost descendants", [&]() {
TSInputEdit edit;
edit.start_byte = 1;
edit.bytes_removed = 0;
edit.bytes_added = 1;
edit.start_point = {0, 1};
edit.extent_removed = {0, 0};
edit.extent_added = {0, 1};
ts_tree_edit(tree, &edit);
assert_consistent(tree);
AssertThat(tree->has_changes, IsTrue());
AssertThat(tree->padding, Equals<Length>({3, 0, {0, 3}}));
AssertThat(tree->size, Equals<Length>({13, 13, {0, 13}}));
AssertThat(tree->children[0]->has_changes, IsTrue());
AssertThat(tree->children[0]->padding, Equals<Length>({3, 0, {0, 3}}));
AssertThat(tree->children[0]->size, Equals<Length>({3, 3, {0, 3}}));
AssertThat(tree->children[1]->has_changes, IsFalse());
AssertThat(tree->children[1]->padding, Equals<Length>({2, 2, {0, 2}}));
AssertThat(tree->children[1]->size, Equals<Length>({3, 3, {0, 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", [&]() {
TSInputEdit edit;
edit.start_byte = 1;
edit.bytes_removed = 3;
edit.bytes_added = 4;
edit.start_point = {0, 1};
edit.extent_removed = {0, 3};
edit.extent_added = {0, 4};
ts_tree_edit(tree, &edit);
assert_consistent(tree);
AssertThat(tree->has_changes, IsTrue());
AssertThat(tree->padding, Equals<Length>({5, 0, {0, 5}}));
AssertThat(tree->size, Equals<Length>({11, 0, {0, 11}}));
AssertThat(tree->children[0]->has_changes, IsTrue());
AssertThat(tree->children[0]->padding, Equals<Length>({5, 0, {0, 5}}));
AssertThat(tree->children[0]->size, Equals<Length>({1, 0, {0, 1}}));
});
});
describe("insertions at the edge of a tree's padding", [&]() {
it("expands the tree's padding", [&]() {
TSInputEdit edit;
edit.start_byte = 2;
edit.bytes_removed = 0;
edit.bytes_added = 2;
edit.start_point = {0, 2};
edit.extent_removed = {0, 0};
edit.extent_added = {0, 2};
ts_tree_edit(tree, &edit);
assert_consistent(tree);
assert_consistent(tree);
AssertThat(tree->has_changes, IsTrue());
AssertThat(tree->padding, Equals<Length>({4, 0, {0, 4}}));
AssertThat(tree->size, Equals<Length>({13, 13, {0, 13}}));
AssertThat(tree->children[0]->has_changes, IsTrue());
AssertThat(tree->children[0]->padding, Equals<Length>({4, 0, {0, 4}}));
AssertThat(tree->children[0]->size, Equals<Length>({3, 3, {0, 3}}));
AssertThat(tree->children[1]->has_changes, IsFalse());
});
});
describe("replacements starting at the edge of a tree's padding", [&]() {
it("resizes the content and not the padding", [&]() {
TSInputEdit edit;
edit.start_byte = 2;
edit.bytes_removed = 2;
edit.bytes_added = 5;
edit.start_point = {0, 2};
edit.extent_removed = {0, 2};
edit.extent_added = {0, 5};
ts_tree_edit(tree, &edit);
assert_consistent(tree);
AssertThat(tree->has_changes, IsTrue());
AssertThat(tree->padding, Equals<Length>({2, 2, {0, 2}}));
AssertThat(tree->size, Equals<Length>({16, 0, {0, 16}}));
AssertThat(tree->children[0]->has_changes, IsTrue());
AssertThat(tree->children[0]->padding, Equals<Length>({2, 2, {0, 2}}));
AssertThat(tree->children[0]->size, Equals<Length>({6, 0, {0, 6}}));
AssertThat(tree->children[1]->has_changes, IsFalse());
});
});
describe("deletions that span more than one child node", [&]() {
it("shrinks subsequent child nodes", [&]() {
TSInputEdit edit;
edit.start_byte = 1;
edit.bytes_removed = 10;
edit.bytes_added = 3;
edit.start_point = {0, 1};
edit.extent_removed = {0, 10};
edit.extent_added = {0, 3};
ts_tree_edit(tree, &edit);
assert_consistent(tree);
assert_consistent(tree);
AssertThat(tree->has_changes, IsTrue());
AssertThat(tree->padding, Equals<Length>({4, 0, {0, 4}}));
AssertThat(tree->size, Equals<Length>({4, 0, {0, 4}}));
AssertThat(tree->children[0]->has_changes, IsTrue());
AssertThat(tree->children[0]->padding, Equals<Length>({4, 0, {0, 4}}));
AssertThat(tree->children[0]->size, Equals<Length>({0, 0, {0, 0}}));
AssertThat(tree->children[1]->has_changes, IsTrue());
AssertThat(tree->children[1]->padding, Equals<Length>({0, 0, {0, 0}}));
AssertThat(tree->children[1]->size, Equals<Length>({0, 0, {0, 0}}));
AssertThat(tree->children[2]->has_changes, IsTrue());
AssertThat(tree->children[2]->padding, Equals<Length>({1, 0, {0, 1}}));
AssertThat(tree->children[2]->size, Equals<Length>({3, 3, {0, 3}}));
});
});
});
describe("eq", [&]() {
Tree *leaf;
before_each([&]() {
leaf = ts_tree_make_leaf(symbol1, {2, 1, {0, 1}}, {5, 4, {0, 4}}, visible);
});
after_each([&]() {
ts_tree_release(leaf);
});
it("returns true for identical trees", [&]() {
Tree *leaf_copy = ts_tree_make_leaf(symbol1, {2, 1, {1, 1}}, {5, 4, {1, 4}}, visible);
AssertThat(ts_tree_eq(leaf, leaf_copy), IsTrue());
Tree *parent = ts_tree_make_node(symbol2, 2, tree_array({
leaf,
leaf_copy,
}), visible);
ts_tree_retain(leaf);
ts_tree_retain(leaf_copy);
Tree *parent_copy = ts_tree_make_node(symbol2, 2, tree_array({
leaf,
leaf_copy,
}), visible);
ts_tree_retain(leaf);
ts_tree_retain(leaf_copy);
AssertThat(ts_tree_eq(parent, parent_copy), IsTrue());
ts_tree_release(leaf_copy);
ts_tree_release(parent);
ts_tree_release(parent_copy);
});
it("returns false for trees with different symbols", [&]() {
Tree *different_leaf = ts_tree_make_leaf(
leaf->symbol + 1,
leaf->padding,
leaf->size,
visible);
AssertThat(ts_tree_eq(leaf, different_leaf), IsFalse());
ts_tree_release(different_leaf);
});
it("returns false for trees with different options", [&]() {
Tree *different_leaf = ts_tree_make_leaf(symbol1, leaf->padding, leaf->size, invisible);
AssertThat(ts_tree_eq(leaf, different_leaf), IsFalse());
ts_tree_release(different_leaf);
});
it("returns false for trees with different sizes", [&]() {
Tree *different_leaf = ts_tree_make_leaf(symbol1, {2, 1, {0, 1}}, leaf->size, invisible);
AssertThat(ts_tree_eq(leaf, different_leaf), IsFalse());
ts_tree_release(different_leaf);
different_leaf = ts_tree_make_leaf(symbol1, leaf->padding, {5, 4, {1, 10}}, invisible);
AssertThat(ts_tree_eq(leaf, different_leaf), IsFalse());
ts_tree_release(different_leaf);
});
it("returns false for trees with different children", [&]() {
Tree *leaf2 = ts_tree_make_leaf(symbol2, {1, 1, {0, 1}}, {3, 3, {0, 3}}, visible);
Tree *parent = ts_tree_make_node(symbol2, 2, tree_array({
leaf,
leaf2,
}), visible);
ts_tree_retain(leaf);
ts_tree_retain(leaf2);
Tree *different_parent = ts_tree_make_node(symbol2, 2, tree_array({
leaf2,
leaf,
}), visible);
ts_tree_retain(leaf2);
ts_tree_retain(leaf);
AssertThat(ts_tree_eq(different_parent, parent), IsFalse());
AssertThat(ts_tree_eq(parent, different_parent), IsFalse());
ts_tree_release(leaf2);
ts_tree_release(parent);
ts_tree_release(different_parent);
});
});
describe("last_external_token_state", [&]() {
Length padding = {1, 1, {0, 1}};
Length size = {2, 2, {0, 2}};
auto make_external = [](Tree *tree) {
tree->has_external_tokens = true;
tree->has_external_token_state = true;
return tree;
};
it("returns the last serialized external token state in the given tree", [&]() {
Tree *tree1, *tree2, *tree3, *tree4, *tree5, *tree6, *tree7, *tree8, *tree9;
tree1 = ts_tree_make_node(symbol1, 2, tree_array({
(tree2 = ts_tree_make_node(symbol2, 3, tree_array({
(tree3 = make_external(ts_tree_make_leaf(symbol3, padding, size, visible))),
(tree4 = ts_tree_make_leaf(symbol4, padding, size, visible)),
(tree5 = ts_tree_make_leaf(symbol5, padding, size, visible)),
}), visible)),
(tree6 = ts_tree_make_node(symbol6, 2, tree_array({
(tree7 = ts_tree_make_node(symbol7, 1, tree_array({
(tree8 = ts_tree_make_leaf(symbol8, padding, size, visible)),
}), visible)),
(tree9 = ts_tree_make_leaf(symbol9, padding, size, visible)),
}), visible)),
}), visible);
auto state = ts_tree_last_external_token_state(tree1);
AssertThat(state, Equals(&tree3->external_token_state));
});
});
});
END_TEST