ParseTree

This commit is contained in:
2026-09-27 01:05:03 -06:00
parent df2a7c8289
commit 5dd0c85d78
7 changed files with 822 additions and 36 deletions
+132 -7
View File
@@ -1,14 +1,56 @@
#include <iostream>
#include <spider/compiler/Compiler.hpp>
#include <spider/compiler/common.hpp>
#include <spider/compiler/text/unicode.hpp>
#include <spider/compiler/text/TextReader.hpp>
#include <spider/compiler/assembler/AsmEBNF.hpp>
#include <spider/compiler/text/ParseTree.hpp>
using namespace spider;
// ============================================================================
// Compiler Entry Points
// ============================================================================
namespace spider {
TokenFactory& assemblyGrammar() {
static TokenFactory grammar;
static bool loaded = false;
if (!loaded) {
asm_ebnf::initTokens(grammar);
loaded = true;
}
return grammar;
}
bool compileProgram(const std::string& source, ParseTree& out) {
return out.parse(asm_ebnf::program, source);
}
}
// ============================================================================
// Test Bookkeeping
// ============================================================================
// Suite wide bookkeeping, so a failing case can never hide behind a clean exit code.
static isize total_tests = 0;
static isize failed_tests = 0;
static void report(bool ok, const std::string& name) {
++total_tests;
if (ok) {
std::cout << "[PASS] " << name << "\n";
} else {
++failed_tests;
std::cerr << "[FAIL] " << name << "\n";
}
}
// Inline evaluator that executes the reader and prints standard output
static bool run_test_case(const Token* token, const std::string& input, bool expectedSuccess, const std::u32string& expectedMatch = U"") {
StringTextReader reader(input);
@@ -17,6 +59,7 @@ static bool run_test_case(const Token* token, const std::string& input, bool exp
bool status_ok = (res.success == expectedSuccess);
bool match_ok = (!expectedSuccess) || expectedMatch.empty() || (res.flatMatch() == expectedMatch);
++total_tests;
if (status_ok && match_ok) {
std::cout << "[PASS] Input: \"" << input << "\" -> "
<< (res.success ? "SUCCESS" : "FAILURE")
@@ -30,6 +73,7 @@ static bool run_test_case(const Token* token, const std::string& input, bool exp
if (expectedSuccess && !expectedMatch.empty()) {
std::cerr << " Expected Match: \"" << unicode::toUTF8(expectedMatch) << "\", Got: \"" << unicode::toUTF8(res.flatMatch()) << "\"\n";
}
++failed_tests;
return false;
}
@@ -137,14 +181,85 @@ void test_full_program(TokenFactory& tf) {
" @align(16) ADD R1, [R2 + R3 * 8 + 4]\n"
" JMP start\n";
StringTextReader reader(asm_code);
TokenResult res = asm_ebnf::program->test(reader);
ParseTree tree;
report(compileProgram(asm_code, tree), "full assembly program parses completely");
}
if (res.success) {
std::cout << "[PASS] Full Assembly Program parsed successfully!\n";
} else {
std::cerr << "[FAIL] Program parsing failed.\n";
void test_parse_tree(TokenFactory& tf) {
std::cout << "\n--- Testing Parse Tree Inspection ---\n";
const std::string asm_code =
"#include stdio\n"
"\n"
"start:\n"
" MOV R1, 0x20 ; Load constant\n"
" @align(16) ADD R1, [R2 + R3 * 8 + 4]\n"
" JMP start\n";
ParseTree tree;
report(compileProgram(asm_code, tree), "program becomes a tree of nodes");
ParseNode* root = tree.root();
report(root != nullptr, "tree exposes a root node");
if (root == nullptr) return;
report(root->hasTag("program"), "root node is tagged as program");
report(root->textUtf8() == asm_code, "root text covers the whole source");
report(root->childCount() > 0, "root holds child nodes");
vector<ParseNode*> lines = root->findAll("line");
report(lines.size() == 6, "program holds one node per line");
if (lines.size() == 6) {
report(lines.front()->parentNode() == root, "a line knows its parent");
report(lines.front()->depth() == 1, "lines sit one level under the root");
report(lines.front()->nextSibling() == lines[1], "sibling walk reaches the next line");
report(lines[1]->previousSibling() == lines.front(), "sibling walk goes back");
report(lines.back()->nextSibling() == nullptr, "the last line has no next sibling");
ParseNode* preprocessor = lines.front()->firstChild("preprocessor");
report(preprocessor != nullptr, "first line holds a preprocessor child");
report(preprocessor != nullptr && preprocessor->textUtf8() == "#include stdio", "preprocessor text is readable");
report(lines[2]->firstChild("line_label") != nullptr, "label line is recognized");
ParseNode* label = lines[2]->find("label");
report(label != nullptr && label->textUtf8() == "start:", "label text is readable");
ParseNode* mov = lines[3]->find("instruction");
report(mov != nullptr, "instruction is found inside its line");
if (mov != nullptr) {
report(mov->firstChild("opcode") != nullptr, "instruction exposes its opcode child");
ParseNode* operands = mov->firstChild("operand_list");
report(operands != nullptr && operands->childCount() == 2, "operand list exposes both operands");
ParseNode* hexlit = mov->find("hex_lit");
report(hexlit != nullptr && hexlit->textUtf8() == "0x20", "hex literal text is readable");
}
ParseNode* comment = lines[3]->firstChild("comment");
report(comment != nullptr && comment->textUtf8() == "; Load constant", "trailing comment is captured");
report(lines[4]->firstChild("line_annotation") != nullptr, "annotation line is recognized");
ParseNode* displacement = lines[4]->find("addrm_dis");
report(displacement != nullptr && displacement->textUtf8() == "[R2 + R3 * 8 + 4]", "displacement operand is readable");
report(tree.count("register") == 4, "every register of the program is reachable");
}
ParseTree single;
report(single.parse(asm_ebnf::instruction, "MOV R1, [R2 + 4]"), "a single instruction parses on its own");
ParseNode* instruction = single.root();
report(instruction != nullptr && instruction->hasTag("instruction"), "instruction root is tagged");
if (instruction != nullptr) {
ParseNode* operands = instruction->firstChild("operand_list");
report(operands != nullptr && operands->childCount() == 2, "operand list holds two operands");
ParseNode* second = operands == nullptr ? nullptr : operands->childAt(1);
report(second != nullptr && second->textUtf8() == "[R2 + 4]", "operand text is readable");
report(second != nullptr && second->ancestor("instruction") == instruction, "an operand can climb back to its instruction");
}
std::cout << "\n--- Parsed Tree ---\n" << tree.toString() << "\n";
}
// ============================================================================
@@ -153,7 +268,7 @@ void test_full_program(TokenFactory& tf) {
int main() {
TokenFactory tf;
asm_ebnf::initTokens(tf);
assemblyGrammar();
std::cout << "Running Token Framework Tests...\n";
@@ -164,6 +279,16 @@ int main() {
test_addressing_modes(tf);
test_instructions_and_lines(tf);
test_full_program(tf);
test_parse_tree(tf);
std::cout << "\n========================================\n";
std::cout << "Test Results: " << (total_tests - failed_tests) << "/" << total_tests << " passed.\n";
std::cout << "========================================\n";
if (failed_tests > 0) {
std::cerr << failed_tests << " test(s) FAILED.\n";
return 1;
}
std::cout << "All token framework tests passed successfully!\n";
return 0;