Rename spec -> test
'Test' is a lot more straightforward of a name.
This commit is contained in:
parent
7d8daf573e
commit
6dc0ff359d
109 changed files with 44 additions and 44 deletions
372
test/runtime/document_test.cc
Normal file
372
test/runtime/document_test.cc
Normal 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
436
test/runtime/node_test.cc
Normal 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
479
test/runtime/parser_test.cc
Normal 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
571
test/runtime/stack_test.cc
Normal 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
439
test/runtime/tree_test.cc
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue