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

12
test/helpers/dedent.h Normal file
View file

@ -0,0 +1,12 @@
#include "compiler/util/string_helpers.h"
#include <string>
static std::string dedent(std::string input) {
size_t indent_level = input.find_first_not_of("\n ") - input.find_first_not_of("\n");
std::string whitespace = "\n" + std::string(indent_level, ' ');
tree_sitter::util::str_replace(&input, whitespace, "\n");
return input.substr(
input.find_first_not_of("\n "),
input.find_last_not_of("\n ") + 1
);
}

View file

@ -0,0 +1,58 @@
#include "helpers/encoding_helpers.h"
#include "runtime/utf16.h"
#include <assert.h>
#include "utf8proc.h"
static inline int string_iterate(TSInputEncoding encoding, const uint8_t *string, size_t length, int32_t *code_point) {
if (encoding == TSInputEncodingUTF8)
return utf8proc_iterate(string, length, code_point);
else
return utf16_iterate(string, length, code_point);
}
size_t string_char_count(TSInputEncoding encoding, const std::string &input) {
const char *string = input.data();
size_t size = input.size();
size_t character = 0, byte = 0;
while (byte < size) {
int32_t code_point;
byte += string_iterate(encoding, (uint8_t *)string + byte, size - byte, &code_point);
character++;
}
return character;
}
long string_byte_for_character(TSInputEncoding encoding, const std::string &input, size_t byte_offset, size_t goal_character) {
const char *string = input.data() + byte_offset;
size_t size = input.size() - byte_offset;
size_t character = 0, byte = 0;
while (character < goal_character) {
if (byte >= size)
return -1;
int32_t code_point;
byte += string_iterate(encoding, (uint8_t *)string + byte, size - byte, &code_point);
character++;
}
return byte;
}
size_t utf8_char_count(const std::string &input) {
return string_char_count(TSInputEncodingUTF8, input);
}
size_t utf16_char_count(const std::string &input) {
return string_char_count(TSInputEncodingUTF16, input);
}
long utf8_byte_for_character(const std::string &input, size_t byte_offset, size_t goal_character) {
return string_byte_for_character(TSInputEncodingUTF8, input, byte_offset, goal_character);
}
long utf16_byte_for_character(const std::string &input, size_t byte_offset, size_t goal_character) {
return string_byte_for_character(TSInputEncodingUTF16, input, byte_offset, goal_character);
}

View file

@ -0,0 +1,15 @@
#ifndef HELPERS_ENCODING_HELPERS_H_
#define HELPERS_ENCODING_HELPERS_H_
#include <string>
#include "tree_sitter/runtime.h"
size_t string_char_count(TSInputEncoding, const std::string &);
size_t utf8_char_count(const std::string &);
size_t utf16_char_count(const std::string &);
long string_byte_for_character(TSInputEncoding, const std::string &, size_t byte_offset, size_t character);
long utf8_byte_for_character(const std::string &, size_t byte_offset, size_t character);
long utf16_byte_for_character(const std::string &, size_t byte_offset, size_t character);
#endif // HELPERS_ENCODING_HELPERS_H_

View file

@ -0,0 +1,37 @@
#ifndef HELPERS_EQUALS_POINTER_H_
#define HELPERS_EQUALS_POINTER_H_
#include "bandit/bandit.h"
#include <string>
namespace snowhouse {
using namespace std;
template<typename ExpectedType>
struct EqualsPointerConstraint : Expression<EqualsPointerConstraint<ExpectedType>> {
EqualsPointerConstraint(const ExpectedType& expected) : expected(expected) {}
template<typename ActualType>
bool operator()(const ActualType& actual) const {
return *expected == *actual;
}
ExpectedType expected;
};
template<typename ExpectedType>
struct Stringizer<EqualsPointerConstraint<ExpectedType>> {
static string ToString(const EqualsPointerConstraint<ExpectedType>& constraint) {
ostringstream builder;
builder << "pointer to " << snowhouse::Stringize(constraint.expected);
return builder.str();
}
};
template<typename ExpectedType>
inline EqualsPointerConstraint<ExpectedType> EqualsPointer(const ExpectedType& expected) {
return EqualsPointerConstraint<ExpectedType>(expected);
}
}
#endif // HELPERS_EQUALS_POINTER_H_

View file

@ -0,0 +1,61 @@
#include "helpers/file_helpers.h"
#include <sys/stat.h>
#include <errno.h>
#include <fstream>
#include <dirent.h>
using std::string;
using std::ifstream;
using std::istreambuf_iterator;
using std::ofstream;
using std::vector;
bool file_exists(const string &path) {
struct stat file_stat;
return stat(path.c_str(), &file_stat) == 0;
}
int get_modified_time(const string &path) {
struct stat file_stat;
if (stat(path.c_str(), &file_stat) != 0) {
if (errno != ENOENT)
fprintf(stderr, "Error in stat() for path: %s\n", + path.c_str());
return 0;
}
return file_stat.st_mtime;
}
string read_file(const string &path) {
ifstream file(path);
istreambuf_iterator<char> file_iterator(file), end_iterator;
string content(file_iterator, end_iterator);
file.close();
return content;
}
void write_file(const string &path, const string &content) {
ofstream file(path);
file << content;
file.close();
}
vector<string> list_directory(const string &path) {
vector<string> result;
DIR *dir = opendir(path.c_str());
if (!dir) {
printf("\nTest error - no such directory '%s'", path.c_str());
return result;
}
struct dirent *dir_entry;
while ((dir_entry = readdir(dir))) {
string name(dir_entry->d_name);
if (name != "." && name != "..") {
result.push_back(name);
}
}
closedir(dir);
return result;
}

View file

@ -0,0 +1,14 @@
#ifndef HELPERS_FILE_HELPERS_H_
#define HELPERS_FILE_HELPERS_H_
#include <string>
#include <vector>
#include <sys/stat.h>
bool file_exists(const std::string &path);
int get_modified_time(const std::string &path);
std::string read_file(const std::string &path);
void write_file(const std::string &path, const std::string &content);
std::vector<std::string> list_directory(const std::string &path);
#endif // HELPERS_FILE_HELPERS_H_

View file

@ -0,0 +1,185 @@
#include "test_helper.h"
#include "helpers/load_language.h"
#include "helpers/file_helpers.h"
#include <unistd.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <map>
#include <string>
#include <fstream>
#include <stdlib.h>
#include "tree_sitter/compiler.h"
using std::map;
using std::string;
using std::ifstream;
using std::ofstream;
using std::istreambuf_iterator;
map<string, const TSLanguage *> loaded_languages;
int libcompiler_mtime = -1;
int compile_result_count = 0;
const char *libcompiler_path =
#if defined(__linux)
"out/Test/obj.target/libcompiler.a";
#else
"out/Test/libcompiler.a";
#endif
static std::string run_command(const char *cmd, const char *args[]) {
int child_pid = fork();
if (child_pid < 0) {
return "fork failed";
}
if (child_pid == 0) {
close(0);
dup2(1, 0);
dup2(2, 1);
dup2(1, 2);
execvp(cmd, (char * const * )args);
}
int status;
do {
waitpid(child_pid, &status, 0);
} while (!WIFEXITED(status));
if (WEXITSTATUS(status) == 0) {
return "";
} else {
return "command failed";
}
}
static const TSLanguage *load_language(const string &source_filename,
const string &lib_filename,
const string &language_name,
string external_scanner_filename = "") {
string language_function_name = "tree_sitter_" + language_name;
string header_dir = getenv("PWD") + string("/include");
int source_mtime = get_modified_time(source_filename);
int header_mtime = get_modified_time(header_dir + "/tree_sitter/parser.h");
int lib_mtime = get_modified_time(lib_filename);
int external_scanner_mtime = get_modified_time(external_scanner_filename);
if (!header_mtime || lib_mtime < header_mtime || lib_mtime < source_mtime ||
lib_mtime < external_scanner_mtime) {
const char *compiler_name = getenv("CXX");
if (!compiler_name) compiler_name = "c++";
vector<const char *> compile_args = {
compiler_name,
"-shared",
"-fPIC",
"-I", header_dir.c_str(),
"-o", lib_filename.c_str(),
"-x", "c",
source_filename.c_str()
};
if (!external_scanner_filename.empty()) {
compile_args.push_back("-g");
string extension = external_scanner_filename.substr(external_scanner_filename.rfind("."));
if (extension == ".c") {
compile_args.push_back("-xc");
} else {
compile_args.push_back("-xc++");
}
compile_args.push_back(external_scanner_filename.c_str());
}
compile_args.push_back(nullptr);
string compile_error = run_command(compiler_name, compile_args.data());
if (!compile_error.empty()) {
AssertThat(string(compile_error), IsEmpty());
return nullptr;
}
}
void *parser_lib = dlopen(lib_filename.c_str(), RTLD_NOW);
if (!parser_lib) {
std::string message(dlerror());
AssertThat(message, IsEmpty());
return nullptr;
}
void *language_function = dlsym(parser_lib, language_function_name.c_str());
if (!language_function) {
std::string message(dlerror());
AssertThat(message, IsEmpty());
return nullptr;
}
return reinterpret_cast<TSLanguage *(*)()>(language_function)();
}
const TSLanguage *load_test_language(const string &name,
const TSCompileResult &compile_result,
string external_scanner_path) {
if (compile_result.error_type != TSCompileErrorTypeNone) {
Assert::Failure(string("Compilation failed ") + compile_result.error_message);
return nullptr;
}
mkdir("out/tmp", 0777);
string source_filename = "out/tmp/compile-result-" + to_string(compile_result_count) + ".c";
string lib_filename = source_filename + ".so";
compile_result_count++;
ofstream source_file;
source_file.open(source_filename);
source_file << compile_result.code;
source_file.close();
auto language = load_language(source_filename, lib_filename, name, external_scanner_path);
free(compile_result.code);
return language;
}
const TSLanguage *load_real_language(const string &language_name) {
if (loaded_languages[language_name])
return loaded_languages[language_name];
string language_dir = string("test/fixtures/grammars/") + language_name;
string grammar_filename = language_dir + "/src/grammar.json";
string parser_filename = language_dir + "/src/parser.c";
string external_scanner_filename = language_dir + "/src/scanner.cc";
if (!file_exists(external_scanner_filename)) {
external_scanner_filename = "";
}
int grammar_mtime = get_modified_time(grammar_filename);
if (!grammar_mtime)
return nullptr;
if (libcompiler_mtime == -1) {
libcompiler_mtime = get_modified_time(libcompiler_path);
if (!libcompiler_mtime)
return nullptr;
}
int parser_mtime = get_modified_time(parser_filename);
if (parser_mtime < grammar_mtime || parser_mtime < libcompiler_mtime) {
printf("\n" "Regenerating the %s parser...\n", language_name.c_str());
string grammar_json = read_file(grammar_filename);
TSCompileResult result = ts_compile_grammar(grammar_json.c_str());
if (result.error_type != TSCompileErrorTypeNone) {
fprintf(stderr, "Failed to compile %s grammar: %s\n", language_name.c_str(), result.error_message);
return nullptr;
}
write_file(parser_filename, result.code);
}
mkdir("out/tmp", 0777);
string lib_filename = "out/tmp/" + language_name + ".so";
const TSLanguage *language = load_language(parser_filename, lib_filename, language_name, external_scanner_filename);
loaded_languages[language_name] = language;
return language;
};

View file

@ -0,0 +1,14 @@
#ifndef HELPERS_LOAD_LANGUAGE_H_
#define HELPERS_LOAD_LANGUAGE_H_
#include "tree_sitter/compiler.h"
#include "tree_sitter/runtime.h"
#include <string>
const TSLanguage *load_real_language(const std::string &name);
const TSLanguage *load_test_language(const std::string &name,
const TSCompileResult &compile_result,
std::string external_scanner_path = "");
#endif // HELPERS_LOAD_LANGUAGE_H_

View file

@ -0,0 +1,45 @@
#include "./point_helpers.h"
#include <string>
#include <ostream>
#include "runtime/length.h"
#include "tree_sitter/runtime.h"
using namespace std;
bool operator==(const TSPoint &left, const TSPoint &right) {
return left.row == right.row && left.column == right.column;
}
bool operator==(const TSRange &left, const TSRange &right) {
return left.start == right.start && left.end == right.end;
}
bool operator==(const Length &left, const Length &right) {
return left.bytes == right.bytes &&
left.chars == right.chars &&
left.extent == right.extent;
}
bool operator<(const TSPoint &left, const TSPoint &right) {
if (left.row < right.row) return true;
if (left.row > right.row) return false;
return left.column < right.column;
}
bool operator>(const TSPoint &left, const TSPoint &right) {
return right < left;
}
std::ostream &operator<<(std::ostream &stream, const TSPoint &point) {
return stream << "{" << point.row << ", " << point.column << "}";
}
std::ostream &operator<<(std::ostream &stream, const TSRange &range) {
return stream << "{" << range.start << ", " << range.end << "}";
}
ostream &operator<<(ostream &stream, const Length &length) {
return stream << "{chars:" << length.chars << ", bytes:" <<
length.bytes << ", extent:" << length.extent << "}";
}

View file

@ -0,0 +1,23 @@
#ifndef HELPERS_POINT_HELPERS_H_
#define HELPERS_POINT_HELPERS_H_
#include "runtime/length.h"
#include <ostream>
bool operator==(const TSPoint &left, const TSPoint &right);
bool operator<(const TSPoint &left, const TSPoint &right);
bool operator>(const TSPoint &left, const TSPoint &right);
bool operator==(const TSRange &left, const TSRange &right);
bool operator==(const Length &left, const Length &right);
std::ostream &operator<<(std::ostream &stream, const TSPoint &point);
std::ostream &operator<<(std::ostream &stream, const TSRange &range);
std::ostream &operator<<(std::ostream &stream, const Length &length);
#endif // HELPERS_POINT_HELPERS_H_

View file

@ -0,0 +1,35 @@
#include <string>
#include <stdlib.h>
using std::string;
static string random_string(char min, char max) {
string result;
size_t length = random() % 12;
for (size_t i = 0; i < length; i++) {
char inserted_char = min + (random() % (max - min));
result += inserted_char;
}
return result;
}
static string random_char(string characters) {
size_t index = random() % characters.size();
return string() + characters[index];
}
string random_words(size_t count) {
string result;
bool just_inserted_word = false;
for (size_t i = 0; i < count; i++) {
if (random() % 10 < 6) {
result += random_char("!(){}[]<>+-=");
} else {
if (just_inserted_word)
result += " ";
result += random_string('a', 'z');
just_inserted_word = true;
}
}
return result;
}

View file

@ -0,0 +1,8 @@
#ifndef HELPERS_RANDOM_HELPERS_H_
#define HELPERS_RANDOM_HELPERS_H_
#include <string>
std::string random_words(size_t count);
#endif // HELPERS_RANDOM_HELPERS_H_

View file

@ -0,0 +1,94 @@
#include "helpers/read_test_entries.h"
#include <assert.h>
#include <string>
#include <regex>
#include "helpers/file_helpers.h"
using std::regex;
using std::regex_search;
using std::regex_replace;
using std::regex_constants::extended;
using std::smatch;
using std::string;
using std::vector;
string fixtures_dir = "test/fixtures/";
static string trim_output(const string &input) {
string result(input);
result = regex_replace(result, regex("[\n\t ]+", extended), string(" "));
result = regex_replace(result, regex("^ ", extended), string(""));
result = regex_replace(result, regex(" $", extended), string(""));
result = regex_replace(result, regex("\\) \\)", extended), string("))"));
return result;
}
static vector<TestEntry> parse_test_entries(string content) {
regex header_pattern("===+\n" "([^=]+)\n" "===+\n", extended);
regex separator_pattern("---+\r?\n", extended);
vector<string> descriptions;
vector<string> bodies;
for (;;) {
smatch matches;
if (!regex_search(content, matches, header_pattern) || matches.empty())
break;
string description = matches[1].str();
descriptions.push_back(description);
if (!bodies.empty())
bodies.back().erase(matches.position());
content.erase(0, matches.position() + matches[0].length());
bodies.push_back(content);
}
vector<TestEntry> result;
for (size_t i = 0; i < descriptions.size(); i++) {
string body = bodies[i];
smatch matches;
if (regex_search(body, matches, separator_pattern)) {
result.push_back({
descriptions[i],
body.substr(0, matches.position() - 1),
trim_output(body.substr(matches.position() + matches[0].length()))
});
} else {
puts(("Invalid corpus entry with description: " + descriptions[i]).c_str());
abort();
}
}
return result;
}
vector<TestEntry> read_real_language_corpus(string language_name) {
vector<TestEntry> result;
string test_directory = fixtures_dir + "grammars/" + language_name + "/grammar_test";
for (string &test_filename : list_directory(test_directory)) {
for (TestEntry &entry : parse_test_entries(read_file(test_directory + "/" + test_filename))) {
result.push_back(entry);
}
}
string error_test_filename = fixtures_dir + "/error_corpus/" + language_name + "_errors.txt";
for (TestEntry &entry : parse_test_entries(read_file(error_test_filename))) {
result.push_back(entry);
}
return result;
}
vector<TestEntry> read_test_language_corpus(string language_name) {
vector<TestEntry> result;
string test_directory = fixtures_dir + "test_grammars/" + language_name;
for (string &test_filename : list_directory(test_directory)) {
for (TestEntry &entry : parse_test_entries(read_file(test_directory + "/" + test_filename))) {
result.push_back(entry);
}
}
return result;
}

View file

@ -0,0 +1,16 @@
#ifndef HELPERS_READ_TEST_ENTRIES_H_
#define HELPERS_READ_TEST_ENTRIES_H_
#include <string>
#include <vector>
struct TestEntry {
std::string description;
std::string input;
std::string tree_string;
};
std::vector<TestEntry> read_real_language_corpus(std::string name);
std::vector<TestEntry> read_test_language_corpus(std::string name);
#endif

View file

@ -0,0 +1,84 @@
#include <stdlib.h>
#include <map>
#include <set>
#include "bandit/bandit.h"
using std::map;
using std::set;
static bool _enabled = false;
static size_t _allocation_count = 0;
static map<void *, size_t> _outstanding_allocations;
namespace record_alloc {
void start() {
_enabled = true;
_allocation_count = 0;
_outstanding_allocations.clear();
}
void stop() {
_enabled = false;
}
set<size_t> outstanding_allocation_indices() {
set<size_t> result;
for (const auto &entry : _outstanding_allocations) {
result.insert(entry.second);
}
return result;
}
size_t allocation_count() {
return _allocation_count;
}
} // namespace record_alloc
extern "C" {
static void *record_allocation(void *result) {
if (!_enabled)
return result;
_outstanding_allocations[result] = _allocation_count;
_allocation_count++;
return result;
}
static void record_deallocation(void *pointer) {
if (!_enabled)
return;
auto entry = _outstanding_allocations.find(pointer);
if (entry != _outstanding_allocations.end()) {
_outstanding_allocations.erase(entry);
}
}
void *ts_record_malloc(size_t size) {
return record_allocation(malloc(size));
}
void *ts_record_realloc(void *pointer, size_t size) {
record_deallocation(pointer);
return record_allocation(realloc(pointer, size));
}
void *ts_record_calloc(size_t count, size_t size) {
return record_allocation(calloc(count, size));
}
void ts_record_free(void *pointer) {
free(pointer);
record_deallocation(pointer);
}
bool ts_record_allocations_toggle(bool value) {
bool previous_value = _enabled;
_enabled = value;
return previous_value;
}
}

View file

@ -0,0 +1,16 @@
#ifndef HELPERS_RECORD_ALLOC_H_
#define HELPERS_RECORD_ALLOC_H_
#include <set>
namespace record_alloc {
void start();
void stop();
void fail_at_allocation_index(size_t failure_index);
std::set<size_t> outstanding_allocation_indices();
size_t allocation_count();
} // namespace record_alloc
#endif // HELPERS_RECORD_ALLOC_H_

View file

@ -0,0 +1,62 @@
#include "rule_helpers.h"
#include <memory>
#include "compiler/rules/symbol.h"
#include "compiler/variable.h"
#include "compiler/lexical_grammar.h"
namespace tree_sitter {
using std::make_shared;
using std::set;
using std::map;
using std::ostream;
using std::string;
using std::to_string;
using rules::Symbol;
rule_ptr character(const set<uint32_t> &ranges) {
return character(ranges, true);
}
rule_ptr character(const set<uint32_t> &chars, bool sign) {
rules::CharacterSet result;
if (sign) {
for (uint32_t c : chars)
result.include(c);
} else {
result.include_all();
for (uint32_t c : chars)
result.exclude(c);
}
return result.copy();
}
rule_ptr i_sym(size_t index) {
return make_shared<Symbol>(index, Symbol::NonTerminal);
}
rule_ptr i_token(size_t index) {
return make_shared<Symbol>(index, Symbol::Terminal);
}
rule_ptr metadata(rule_ptr rule, rules::MetadataParams params) {
return rules::Metadata::build(rule, params);
}
rule_ptr active_prec(int precedence, rule_ptr rule) {
rules::MetadataParams params;
params.precedence = precedence;
params.has_precedence = true;
params.is_active = true;
return rules::Metadata::build(rule, params);
}
bool operator==(const Variable &left, const Variable &right) {
return left.name == right.name && left.rule->operator==(*right.rule) &&
left.type == right.type;
}
bool operator==(const LexicalVariable &left, const LexicalVariable &right) {
return left.name == right.name && left.rule->operator==(*right.rule) &&
left.type == right.type && left.is_string == right.is_string;
}
}

View file

@ -0,0 +1,25 @@
#ifndef HELPERS_RULE_HELPERS_H_
#define HELPERS_RULE_HELPERS_H_
#include "tree_sitter/compiler.h"
#include "compiler/rules.h"
#include "compiler/rules/character_set.h"
#include "compiler/rules/metadata.h"
#include "compiler/variable.h"
namespace tree_sitter {
rule_ptr metadata(rule_ptr, rules::MetadataParams params);
rule_ptr character(const std::set<uint32_t> &);
rule_ptr character(const std::set<uint32_t> &, bool sign);
rule_ptr i_sym(size_t index);
rule_ptr i_token(size_t index);
rule_ptr active_prec(int precedence, rule_ptr);
struct Variable;
struct LexicalVariable;
bool operator==(const Variable &left, const Variable &right);
bool operator==(const LexicalVariable &left, const LexicalVariable &right);
}
#endif // HELPERS_RULE_HELPERS_H_

View file

@ -0,0 +1,106 @@
#include "./scope_sequence.h"
#include "bandit/bandit.h"
#include <sstream>
#include "helpers/stream_methods.h"
#include "helpers/point_helpers.h"
using std::string;
using std::cout;
static void append_text_to_scope_sequence(ScopeSequence *sequence,
ScopeStack *current_scopes,
const std::string &text,
size_t length) {
for (size_t i = 0; i < length; i++) {
string character(1, text[sequence->size()]);
sequence->push_back(*current_scopes);
sequence->back().push_back("'" + character + "'");
}
}
static void append_to_scope_sequence(ScopeSequence *sequence,
ScopeStack *current_scopes,
TSNode node, TSDocument *document,
const std::string &text) {
append_text_to_scope_sequence(
sequence, current_scopes, text, ts_node_start_byte(node) - sequence->size()
);
current_scopes->push_back(ts_node_type(node, document));
for (size_t i = 0, n = ts_node_child_count(node); i < n; i++) {
TSNode child = ts_node_child(node, i);
append_to_scope_sequence(sequence, current_scopes, child, document, text);
}
append_text_to_scope_sequence(
sequence, current_scopes, text, ts_node_end_byte(node) - sequence->size()
);
current_scopes->pop_back();
}
ScopeSequence build_scope_sequence(TSDocument *document, const std::string &text) {
ScopeSequence sequence;
ScopeStack current_scopes;
TSNode node = ts_document_root_node(document);
append_to_scope_sequence(&sequence, &current_scopes, node, document, text);
return sequence;
}
bool operator<=(const TSPoint &left, const TSPoint &right) {
if (left.row < right.row)
return true;
else if (left.row == right.row)
return left.column <= right.column;
else
return false;
}
void verify_changed_ranges(const ScopeSequence &old_sequence, const ScopeSequence &new_sequence,
const string &text, TSRange *ranges, size_t range_count) {
TSPoint current_position = {0, 0};
for (size_t i = 0; i < old_sequence.size(); i++) {
if (text[i] == '\n') {
current_position.row++;
current_position.column = 0;
continue;
}
const ScopeStack &old_scopes = old_sequence[i];
const ScopeStack &new_scopes = new_sequence[i];
if (old_scopes != new_scopes) {
bool found_containing_range = false;
for (size_t j = 0; j < range_count; j++) {
TSRange range = ranges[j];
if (range.start <= current_position && current_position <= range.end) {
found_containing_range = true;
break;
}
}
if (!found_containing_range) {
std::stringstream message_stream;
message_stream << "Found changed scope outside of any invalidated range;\n";
message_stream << "Position: " << current_position << "\n";
message_stream << "Byte index: " << i << "\n";
size_t line_start_index = i - current_position.column;
size_t line_end_index = text.find_first_of('\n', i);
message_stream << "Line: " << text.substr(line_start_index, line_end_index - line_start_index) << "\n";
for (size_t j = 0; j < current_position.column + string("Line: ").size(); j++)
message_stream << " ";
message_stream << "^\n";
message_stream << "Old scopes: " << old_scopes << "\n";
message_stream << "New scopes: " << new_scopes << "\n";
message_stream << "Invalidated ranges:\n";
for (size_t j = 0; j < range_count; j++) {
message_stream << " " << ranges[j] << "\n";
}
Assert::Failure(message_stream.str());
}
}
current_position.column++;
}
}

View file

@ -0,0 +1,16 @@
#ifndef HELPERS_SCOPE_SEQUENCE_H_
#define HELPERS_SCOPE_SEQUENCE_H_
#include <string>
#include <vector>
#include "tree_sitter/runtime.h"
typedef std::string Scope;
typedef std::vector<Scope> ScopeStack;
typedef std::vector<ScopeStack> ScopeSequence;
ScopeSequence build_scope_sequence(TSDocument *document, const std::string &text);
void verify_changed_ranges(const ScopeSequence &old, const ScopeSequence &new_sequence, const std::string &text, TSRange *ranges, size_t range_count);
#endif // HELPERS_SCOPE_SEQUENCE_H_

133
test/helpers/spy_input.cc Normal file
View file

@ -0,0 +1,133 @@
#include "helpers/spy_input.h"
#include "helpers/encoding_helpers.h"
#include <string.h>
#include <algorithm>
#include <assert.h>
using std::pair;
using std::string;
static const size_t UTF8_MAX_CHAR_SIZE = 4;
SpyInput::SpyInput(string content, size_t chars_per_chunk) :
chars_per_chunk(chars_per_chunk),
buffer_size(UTF8_MAX_CHAR_SIZE * chars_per_chunk),
buffer(new char[buffer_size]),
byte_offset(0),
content(content),
encoding(TSInputEncodingUTF8),
strings_read({""}) {}
SpyInput::~SpyInput() {
delete[] buffer;
}
const char * SpyInput::read(void *payload, uint32_t *bytes_read) {
auto spy = static_cast<SpyInput *>(payload);
if (spy->byte_offset > spy->content.size()) {
*bytes_read = 0;
return "";
}
long byte_count = string_byte_for_character(spy->encoding, spy->content, spy->byte_offset, spy->chars_per_chunk);
if (byte_count < 0)
byte_count = spy->content.size() - spy->byte_offset;
string result = spy->content.substr(spy->byte_offset, byte_count);
*bytes_read = byte_count;
spy->strings_read.back() += result;
spy->byte_offset += byte_count;
/*
* This class stores its entire `content` in a contiguous buffer, but we want
* to ensure that the code under test cannot accidentally read more than
* `*bytes_read` bytes past the returned pointer. To make sure that this type
* of error does not fly, we copy the chunk into a zeroed-out buffer and
* return a reference to that buffer, rather than a pointer into the main
* content.
*/
memset(spy->buffer, 0, spy->buffer_size);
memcpy(spy->buffer, result.data(), byte_count);
return spy->buffer;
}
int SpyInput::seek(void *payload, uint32_t character, uint32_t byte) {
auto spy = static_cast<SpyInput *>(payload);
if (spy->strings_read.size() == 0 || spy->strings_read.back().size() > 0)
spy->strings_read.push_back("");
spy->byte_offset = byte;
return 0;
}
TSInput SpyInput::input() {
TSInput result;
result.payload = this;
result.encoding = encoding;
result.seek = seek;
result.read = read;
return result;
}
static TSPoint get_extent(string text) {
TSPoint result = {0, 0};
for (auto i = text.begin(); i != text.end(); i++) {
if (*i == '\n') {
result.row++;
result.column = 0;
} else {
result.column++;
}
}
return result;
}
TSInputEdit SpyInput::replace(size_t start_byte, size_t bytes_removed, string text) {
auto swap = swap_substr(start_byte, bytes_removed, text);
size_t bytes_added = text.size();
undo_stack.push_back(SpyInputEdit{start_byte, bytes_added, swap.first});
TSInputEdit result = {};
result.start_byte = start_byte;
result.bytes_added = bytes_added;
result.bytes_removed = bytes_removed;
result.start_point = swap.second;
result.extent_removed = get_extent(swap.first);
result.extent_added = get_extent(text);
return result;
}
TSInputEdit SpyInput::undo() {
SpyInputEdit entry = undo_stack.back();
undo_stack.pop_back();
auto swap = swap_substr(entry.start_byte, entry.bytes_removed, entry.text_inserted);
TSInputEdit result;
result.start_byte = entry.start_byte;
result.bytes_removed = entry.bytes_removed;
result.bytes_added = entry.text_inserted.size();
result.start_point = swap.second;
result.extent_removed = get_extent(swap.first);
result.extent_added = get_extent(entry.text_inserted);
return result;
}
pair<string, TSPoint> SpyInput::swap_substr(size_t start_byte, size_t bytes_removed, string text) {
TSPoint start_position = {0, 0};
for (auto i = content.begin(), n = content.begin() + start_byte; i < n; i++) {
if (*i == '\n') {
start_position.row++;
start_position.column = 0;
} else {
start_position.column++;
}
}
string text_removed = content.substr(start_byte, bytes_removed);
content.erase(start_byte, bytes_removed);
content.insert(start_byte, text);
return {text_removed, start_position};
}
void SpyInput::clear() {
strings_read.clear();
}

39
test/helpers/spy_input.h Normal file
View file

@ -0,0 +1,39 @@
#ifndef HELPERS_SPY_INPUT_H_
#define HELPERS_SPY_INPUT_H_
#include <string>
#include <vector>
#include "tree_sitter/runtime.h"
struct SpyInputEdit {
size_t start_byte;
size_t bytes_removed;
std::string text_inserted;
};
class SpyInput {
uint32_t chars_per_chunk;
uint32_t buffer_size;
char *buffer;
uint32_t byte_offset;
std::vector<SpyInputEdit> undo_stack;
static const char * read(void *, uint32_t *);
static int seek(void *, uint32_t, uint32_t);
std::pair<std::string, TSPoint> swap_substr(size_t, size_t, std::string);
public:
SpyInput(std::string content, size_t chars_per_chunk);
~SpyInput();
TSInput input();
void clear();
TSInputEdit replace(size_t start_char, size_t chars_removed, std::string text);
TSInputEdit undo();
std::string content;
TSInputEncoding encoding;
std::vector<std::string> strings_read;
};
#endif // HELPERS_SPY_INPUT_H_

View file

@ -0,0 +1,22 @@
#include "helpers/spy_logger.h"
#include <string>
#include <vector>
using std::string;
using std::vector;
static void spy_log(void *data, TSLogType type, const char *msg) {
SpyLogger *logger = static_cast<SpyLogger *>(data);
logger->messages.push_back(msg);
}
TSLogger SpyLogger::logger() {
TSLogger result;
result.payload = (void *)this;
result.log = spy_log;
return result;
}
void SpyLogger::clear() {
messages.clear();
}

15
test/helpers/spy_logger.h Normal file
View file

@ -0,0 +1,15 @@
#ifndef HELPERS_SPY_LOGGER_H_
#define HELPERS_SPY_LOGGER_H_
#include <string>
#include <vector>
#include "tree_sitter/runtime.h"
class SpyLogger {
public:
void clear();
TSLogger logger();
std::vector<std::string> messages;
};
#endif // HELPERS_SPY_DEBUGGER_H_

View file

@ -0,0 +1,22 @@
#include "tree_sitter/runtime.h"
#include <stdio.h>
static void log(void *payload, TSLogType type, const char *msg) {
bool include_lexing = (bool)payload;
switch (type) {
case TSLogTypeParse:
fprintf(stderr, "* %s\n", msg);
break;
case TSLogTypeLex:
if (include_lexing)
fprintf(stderr, " %s\n", msg);
break;
}
}
TSLogger stderr_logger_new(bool include_lexing) {
TSLogger result;
result.payload = (void *)include_lexing;
result.log = log;
return result;
}

View file

@ -0,0 +1,8 @@
#ifndef HELPERS_STDERR_LOGGER_H_
#define HELPERS_STDERR_LOGGER_H_
#include "tree_sitter/runtime.h"
TSLogger stderr_logger_new(bool include_lexing);
#endif // HELPERS_STDERR_LOGGER_H_

View file

@ -0,0 +1,146 @@
#include "helpers/stream_methods.h"
#include "test_helper.h"
#include "tree_sitter/compiler.h"
#include "compiler/parse_table.h"
#include "compiler/syntax_grammar.h"
#include "compiler/lexical_grammar.h"
#include "compiler/build_tables/parse_item.h"
#include "compiler/build_tables/lex_item.h"
namespace tree_sitter {
ostream &operator<<(ostream &stream, const Grammar &grammar) {
stream << string("#<grammar");
stream << " rules: " << grammar.rules;
return stream << string("}>");
}
ostream &operator<<(ostream &stream, const CompileError &error) {
if (error.type)
return stream << (string("#<compile-error '") + error.message + "'>");
else
return stream << string("#<no-compile-error>");
}
ostream &operator<<(ostream &stream, const Rule &rule) {
return stream << rule.to_string();
}
ostream &operator<<(ostream &stream, const rule_ptr &rule) {
if (rule.get())
stream << *rule;
else
stream << string("(null-rule)");
return stream;
}
ostream &operator<<(ostream &stream, const Variable &variable) {
return stream << string("{") << variable.name << string(", ") << variable.rule << string(", ") << to_string(variable.type) << string("}");
}
ostream &operator<<(ostream &stream, const SyntaxVariable &variable) {
return stream << string("{") << variable.name << string(", ") << variable.productions << string(", ") << to_string(variable.type) << string("}");
}
ostream &operator<<(ostream &stream, const LexicalVariable &variable) {
return stream << "{" << variable.name << ", " << variable.rule << ", " <<
to_string(variable.type) << ", " << to_string(variable.is_string) << "}";
}
std::ostream &operator<<(std::ostream &stream, const AdvanceAction &action) {
return stream << string("#<advance ") + to_string(action.state_index) + ">";
}
std::ostream &operator<<(std::ostream &stream, const AcceptTokenAction &action) {
return stream << string("#<accept ") + to_string(action.symbol.index) + ">";
}
ostream &operator<<(ostream &stream, const ParseAction &action) {
switch (action.type) {
case ParseActionTypeError:
return stream << string("#<error>");
case ParseActionTypeAccept:
return stream << string("#<accept>");
case ParseActionTypeShift:
return stream << string("#<shift state:") << to_string(action.state_index) << ">";
case ParseActionTypeReduce:
return stream << ("#<reduce sym" + to_string(action.symbol.index) + " " +
to_string(action.consumed_symbol_count) + ">");
default:
return stream;
}
}
ostream &operator<<(ostream &stream, const ParseTableEntry &entry) {
return stream << entry.actions;
}
ostream &operator<<(ostream &stream, const ParseState &state) {
stream << string("#<parse_state terminal_entries:");
stream << state.terminal_entries;
stream << " nonterminal_entries: " << state.nonterminal_entries;
return stream << string(">");
}
ostream &operator<<(ostream &stream, const ExternalToken &external_token) {
return stream << "{" << external_token.name << ", " << external_token.type <<
"," << external_token.corresponding_internal_token << "}";
}
ostream &operator<<(ostream &stream, const ProductionStep &step) {
stream << "(symbol: " << step.symbol << ", precedence:" << to_string(step.precedence);
stream << ", associativity: ";
switch (step.associativity) {
case rules::AssociativityLeft:
return stream << "left)";
case rules::AssociativityRight:
return stream << "right)";
default:
return stream << "none)";
}
}
ostream &operator<<(ostream &stream, const PrecedenceRange &range) {
if (range.empty)
return stream << string("{empty}");
else
return stream << string("{") << to_string(range.min) << string(", ") << to_string(range.max) << string("}");
}
namespace build_tables {
ostream &operator<<(ostream &stream, const LexItem &item) {
return stream << string("(item ") << item.lhs << string(" ") << *item.rule
<< string(")");
}
ostream &operator<<(ostream &stream, const LexItemSet &item_set) {
return stream << item_set.entries;
}
ostream &operator<<(ostream &stream, const LexItemSet::Transition &transition) {
return stream << "{dest: " << transition.destination << ", prec: " << transition.precedence << "}";
}
ostream &operator<<(ostream &stream, const ParseItem &item) {
return stream << string("(item variable:") << to_string(item.variable_index)
<< string(" production:") << to_string((size_t)item.production % 1000)
<< string(" step:") << to_string(item.step_index)
<< string(")");
}
std::ostream &operator<<(std::ostream &stream, const ParseItemSet &item_set) {
return stream << item_set.entries;
}
std::ostream &operator<<(std::ostream &stream, const LookaheadSet &set) {
if (set.entries.get()) {
return stream << *set.entries;
} else {
return stream << "{}";
}
}
} // namespace build_tables
} // namespace tree_sitter

View file

@ -0,0 +1,138 @@
#ifndef HELPERS_STREAM_METHODS_H_
#define HELPERS_STREAM_METHODS_H_
#include <iostream>
#include <set>
#include <unordered_map>
#include <map>
#include <unordered_set>
#include <vector>
#include "compiler/grammar.h"
#include "compiler/compile_error.h"
#include "compiler/build_tables/lex_item.h"
using std::cout;
namespace std {
template<typename T>
inline std::ostream& operator<<(std::ostream &stream, const std::vector<T> &vector) {
stream << std::string("(vector: ");
bool started = false;
for (auto item : vector) {
if (started) stream << std::string(", ");
stream << item;
started = true;
}
return stream << ")";
}
template<typename T>
inline std::ostream& operator<<(std::ostream &stream, const std::set<T> &set) {
stream << std::string("(set: ");
bool started = false;
for (auto item : set) {
if (started) stream << std::string(", ");
stream << item;
started = true;
}
return stream << ")";
}
template<typename T, typename H, typename E>
inline std::ostream& operator<<(std::ostream &stream, const std::unordered_set<T, H, E> &set) {
stream << std::string("(set: ");
bool started = false;
for (auto item : set) {
if (started) stream << std::string(", ");
stream << item;
started = true;
}
return stream << ")";
}
template<typename TKey, typename TValue>
inline std::ostream& operator<<(std::ostream &stream, const std::map<TKey, TValue> &map) {
stream << std::string("(map: ");
bool started = false;
for (auto pair : map) {
if (started) stream << std::string(", ");
stream << pair.first;
stream << std::string(" => ");
stream << pair.second;
started = true;
}
return stream << ")";
}
template<typename TKey, typename TValue>
inline std::ostream& operator<<(std::ostream &stream, const std::unordered_map<TKey, TValue> &map) {
stream << std::string("(map: ");
bool started = false;
for (auto pair : map) {
if (started) stream << std::string(", ");
stream << pair.first;
stream << std::string(" => ");
stream << pair.second;
started = true;
}
return stream << ")";
}
template<typename T1, typename T2>
inline std::ostream& operator<<(std::ostream &stream, const std::pair<T1, T2> &pair) {
return stream << "{" << pair.first << ", " << pair.second << "}";
}
} // namespace std
namespace tree_sitter {
using std::ostream;
using std::string;
using std::to_string;
struct Variable;
struct SyntaxVariable;
struct LexicalVariable;
struct AdvanceAction;
struct AcceptTokenAction;
struct ParseAction;
struct ParseState;
struct ExternalToken;
struct ProductionStep;
struct PrecedenceRange;
ostream &operator<<(ostream &, const Grammar &);
ostream &operator<<(ostream &, const CompileError &);
ostream &operator<<(ostream &, const Rule &);
ostream &operator<<(ostream &, const rule_ptr &);
ostream &operator<<(ostream &, const Variable &);
ostream &operator<<(ostream &, const SyntaxVariable &);
ostream &operator<<(ostream &, const LexicalVariable &);
ostream &operator<<(ostream &, const AdvanceAction &);
ostream &operator<<(ostream &, const AcceptTokenAction &);
ostream &operator<<(ostream &, const ParseAction &);
ostream &operator<<(ostream &, const ParseState &);
ostream &operator<<(ostream &, const ExternalToken &);
ostream &operator<<(ostream &, const ProductionStep &);
ostream &operator<<(ostream &, const PrecedenceRange &);
namespace build_tables {
class LexItem;
class LexItemSet;
struct ParseItem;
struct ParseItemSet;
class LookaheadSet;
ostream &operator<<(ostream &, const LexItem &);
ostream &operator<<(ostream &, const LexItemSet &);
ostream &operator<<(ostream &, const LexItemSet::Transition &);
ostream &operator<<(ostream &, const ParseItem &);
ostream &operator<<(ostream &, const ParseItemSet &);
ostream &operator<<(ostream &, const LookaheadSet &);
} // namespace build_tables
} // namespace tree_sitter
#endif // HELPERS_STREAM_METHODS_H_

View file

@ -0,0 +1,50 @@
#include "helpers/tree_helpers.h"
#include "runtime/document.h"
#include "runtime/node.h"
#include <ostream>
using std::string;
using std::to_string;
using std::ostream;
const char *symbol_names[24] = {
"ERROR", "END", "two", "three", "four", "five", "six", "seven", "eight",
"nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
"sixteen", "seventeen", "eighteen", "nineteen", "twenty", "twenty-one",
"twenty-two", "twenty-three"
};
Tree ** tree_array(std::vector<Tree *> trees) {
Tree ** result = (Tree **)calloc(trees.size(), sizeof(Tree *));
for (size_t i = 0; i < trees.size(); i++)
result[i] = trees[i];
return result;
}
ostream &operator<<(std::ostream &stream, const Tree *tree) {
static TSLanguage DUMMY_LANGUAGE = {};
static TSDocument DUMMY_DOCUMENT = {};
DUMMY_DOCUMENT.parser.language = &DUMMY_LANGUAGE;
DUMMY_LANGUAGE.symbol_names = symbol_names;
TSNode node;
node.data = tree;
return stream << string(ts_node_string(node, &DUMMY_DOCUMENT));
}
ostream &operator<<(ostream &stream, const TSNode &node) {
return stream << string("{") << (const Tree *)node.data <<
string(", ") << to_string(ts_node_start_char(node)) << string("}");
}
bool operator==(const TSNode &left, const TSNode &right) {
return ts_node_eq(left, right);
}
bool operator==(const std::vector<Tree *> &vec, const TreeArray &array) {
if (vec.size() != array.size)
return false;
for (size_t i = 0; i < array.size; i++)
if (array.contents[i] != vec[i])
return false;
return true;
}

View file

@ -0,0 +1,16 @@
#ifndef HELPERS_TREE_HELPERS_H_
#define HELPERS_TREE_HELPERS_H_
#include "runtime/tree.h"
#include <vector>
#include <string>
extern const char *symbol_names[24];
Tree ** tree_array(std::vector<Tree *> trees);
std::ostream &operator<<(std::ostream &stream, const Tree *tree);
std::ostream &operator<<(std::ostream &stream, const TSNode &node);
bool operator==(const TSNode &left, const TSNode &right);
bool operator==(const std::vector<Tree *> &right, const TreeArray &array);
#endif // HELPERS_TREE_HELPERS_H_