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
12
test/helpers/dedent.h
Normal file
12
test/helpers/dedent.h
Normal 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
|
||||
);
|
||||
}
|
||||
58
test/helpers/encoding_helpers.cc
Normal file
58
test/helpers/encoding_helpers.cc
Normal 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);
|
||||
}
|
||||
15
test/helpers/encoding_helpers.h
Normal file
15
test/helpers/encoding_helpers.h
Normal 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_
|
||||
37
test/helpers/equals_pointer.h
Normal file
37
test/helpers/equals_pointer.h
Normal 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_
|
||||
61
test/helpers/file_helpers.cc
Normal file
61
test/helpers/file_helpers.cc
Normal 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;
|
||||
}
|
||||
14
test/helpers/file_helpers.h
Normal file
14
test/helpers/file_helpers.h
Normal 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_
|
||||
185
test/helpers/load_language.cc
Normal file
185
test/helpers/load_language.cc
Normal 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;
|
||||
};
|
||||
14
test/helpers/load_language.h
Normal file
14
test/helpers/load_language.h
Normal 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_
|
||||
45
test/helpers/point_helpers.cc
Normal file
45
test/helpers/point_helpers.cc
Normal 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 << "}";
|
||||
}
|
||||
23
test/helpers/point_helpers.h
Normal file
23
test/helpers/point_helpers.h
Normal 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_
|
||||
35
test/helpers/random_helpers.cc
Normal file
35
test/helpers/random_helpers.cc
Normal 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;
|
||||
}
|
||||
8
test/helpers/random_helpers.h
Normal file
8
test/helpers/random_helpers.h
Normal 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_
|
||||
94
test/helpers/read_test_entries.cc
Normal file
94
test/helpers/read_test_entries.cc
Normal 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;
|
||||
}
|
||||
16
test/helpers/read_test_entries.h
Normal file
16
test/helpers/read_test_entries.h
Normal 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
|
||||
84
test/helpers/record_alloc.cc
Normal file
84
test/helpers/record_alloc.cc
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
16
test/helpers/record_alloc.h
Normal file
16
test/helpers/record_alloc.h
Normal 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_
|
||||
62
test/helpers/rule_helpers.cc
Normal file
62
test/helpers/rule_helpers.cc
Normal 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;
|
||||
}
|
||||
}
|
||||
25
test/helpers/rule_helpers.h
Normal file
25
test/helpers/rule_helpers.h
Normal 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_
|
||||
106
test/helpers/scope_sequence.cc
Normal file
106
test/helpers/scope_sequence.cc
Normal 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, ¤t_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++;
|
||||
}
|
||||
}
|
||||
16
test/helpers/scope_sequence.h
Normal file
16
test/helpers/scope_sequence.h
Normal 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
133
test/helpers/spy_input.cc
Normal 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
39
test/helpers/spy_input.h
Normal 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_
|
||||
22
test/helpers/spy_logger.cc
Normal file
22
test/helpers/spy_logger.cc
Normal 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
15
test/helpers/spy_logger.h
Normal 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_
|
||||
22
test/helpers/stderr_logger.cc
Normal file
22
test/helpers/stderr_logger.cc
Normal 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;
|
||||
}
|
||||
8
test/helpers/stderr_logger.h
Normal file
8
test/helpers/stderr_logger.h
Normal 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_
|
||||
146
test/helpers/stream_methods.cc
Normal file
146
test/helpers/stream_methods.cc
Normal 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
|
||||
138
test/helpers/stream_methods.h
Normal file
138
test/helpers/stream_methods.h
Normal 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_
|
||||
50
test/helpers/tree_helpers.cc
Normal file
50
test/helpers/tree_helpers.cc
Normal 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;
|
||||
}
|
||||
16
test/helpers/tree_helpers.h
Normal file
16
test/helpers/tree_helpers.h
Normal 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_
|
||||
Loading…
Add table
Add a link
Reference in a new issue