Phase 3b: Real tree-sitter integration (Steps 45-49)
Replace TreeSitterParser stubs with real tree-sitter C bindings for Python, C++, and Elisp parsing. Add tree-sitter core via vcpkg and language grammars via FetchContent. Implement CST-to-AST converters with auto-annotation, memory pattern detection, error recovery, and ParseResult/ParseDiagnostic types. All 34 tests pass (6+7+6+8+7). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
220
editor/tests/step48_test.cpp
Normal file
220
editor/tests/step48_test.cpp
Normal file
@@ -0,0 +1,220 @@
|
||||
// Step 48 TDD Test: CST-to-AST Mapping Refinement
|
||||
//
|
||||
// Tests more complex constructs across all three languages:
|
||||
// 1. Python: function with default parameter (def f(x=10))
|
||||
// 2. Python: if-statement in body
|
||||
// 3. Python: for-loop in body
|
||||
// 4. C++: function with multiple statements
|
||||
// 5. C++: void function
|
||||
// 6. Elisp: multiple functions in one source
|
||||
// 7. Python: function with multiple parameters
|
||||
// 8. C++: function with string literal
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include "ast/Parser.h"
|
||||
#include "ast/Annotation.h"
|
||||
|
||||
int main() {
|
||||
int passed = 0;
|
||||
int failed = 0;
|
||||
|
||||
// --- Test 1: Python function with default parameter ---
|
||||
{
|
||||
std::string source = "def f(x=10):\n return x\n";
|
||||
auto module = TreeSitterParser::parsePython(source);
|
||||
|
||||
auto functions = module->getChildren("functions");
|
||||
assert(!functions.empty() && "Should parse function");
|
||||
auto* fn = static_cast<Function*>(functions[0]);
|
||||
assert(fn->name == "f");
|
||||
|
||||
auto params = fn->getChildren("parameters");
|
||||
assert(!params.empty() && "Should have parameter");
|
||||
auto* param = static_cast<Parameter*>(params[0]);
|
||||
assert(param->name == "x" && "Parameter name should be 'x'");
|
||||
|
||||
auto* defVal = param->getChild("defaultValue");
|
||||
assert(defVal != nullptr && "Parameter should have a default value");
|
||||
assert(defVal->conceptType == "IntegerLiteral" &&
|
||||
"Default value should be IntegerLiteral");
|
||||
|
||||
std::cout << "Test 1 PASS: Python default parameter parsed" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 2: Python if-statement in body ---
|
||||
{
|
||||
std::string source =
|
||||
"def check(x):\n"
|
||||
" if x > 0:\n"
|
||||
" return x\n";
|
||||
auto module = TreeSitterParser::parsePython(source);
|
||||
|
||||
auto functions = module->getChildren("functions");
|
||||
assert(!functions.empty());
|
||||
auto* fn = static_cast<Function*>(functions[0]);
|
||||
|
||||
auto body = fn->getChildren("body");
|
||||
assert(!body.empty() && "Body should not be empty");
|
||||
|
||||
bool hasIf = false;
|
||||
for (auto* stmt : body) {
|
||||
if (stmt->conceptType == "IfStatement") {
|
||||
hasIf = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(hasIf && "Body should contain an IfStatement");
|
||||
|
||||
std::cout << "Test 2 PASS: Python if-statement parsed" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 3: Python for-loop in body ---
|
||||
{
|
||||
std::string source =
|
||||
"def loop(n):\n"
|
||||
" for i in range(n):\n"
|
||||
" x = i\n";
|
||||
auto module = TreeSitterParser::parsePython(source);
|
||||
|
||||
auto functions = module->getChildren("functions");
|
||||
assert(!functions.empty());
|
||||
auto* fn = static_cast<Function*>(functions[0]);
|
||||
|
||||
auto body = fn->getChildren("body");
|
||||
assert(!body.empty() && "Body should not be empty");
|
||||
|
||||
bool hasFor = false;
|
||||
for (auto* stmt : body) {
|
||||
if (stmt->conceptType == "ForLoop") {
|
||||
hasFor = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(hasFor && "Body should contain a ForLoop");
|
||||
|
||||
std::cout << "Test 3 PASS: Python for-loop parsed" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 4: C++ function with multiple statements ---
|
||||
{
|
||||
std::string source =
|
||||
"int compute(int a, int b) {\n"
|
||||
" int c = a + b;\n"
|
||||
" return c;\n"
|
||||
"}\n";
|
||||
auto module = TreeSitterParser::parseCpp(source);
|
||||
|
||||
auto functions = module->getChildren("functions");
|
||||
assert(!functions.empty());
|
||||
auto* fn = static_cast<Function*>(functions[0]);
|
||||
assert(fn->name == "compute");
|
||||
|
||||
auto body = fn->getChildren("body");
|
||||
assert(body.size() >= 2 && "Function should have at least 2 body statements");
|
||||
|
||||
std::cout << "Test 4 PASS: C++ multiple statements parsed" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 5: C++ void function ---
|
||||
{
|
||||
std::string source = "void doNothing() {}\n";
|
||||
auto module = TreeSitterParser::parseCpp(source);
|
||||
|
||||
auto functions = module->getChildren("functions");
|
||||
assert(!functions.empty());
|
||||
auto* fn = static_cast<Function*>(functions[0]);
|
||||
assert(fn->name == "doNothing");
|
||||
|
||||
auto* retType = fn->getChild("returnType");
|
||||
assert(retType != nullptr && "Function should have a return type");
|
||||
auto* primType = static_cast<PrimitiveType*>(retType);
|
||||
assert(primType->kind == "void" && "Return type should be 'void'");
|
||||
|
||||
std::cout << "Test 5 PASS: C++ void function parsed" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 6: Elisp multiple functions in one source ---
|
||||
{
|
||||
std::string source =
|
||||
"(defun add (a b) (+ a b))\n"
|
||||
"(defun sub (a b) (- a b))\n";
|
||||
auto module = TreeSitterParser::parseElisp(source);
|
||||
|
||||
auto functions = module->getChildren("functions");
|
||||
assert(functions.size() >= 2 && "Should parse at least 2 functions");
|
||||
|
||||
auto* fn1 = static_cast<Function*>(functions[0]);
|
||||
auto* fn2 = static_cast<Function*>(functions[1]);
|
||||
assert(fn1->name == "add" && "First function should be 'add'");
|
||||
assert(fn2->name == "sub" && "Second function should be 'sub'");
|
||||
|
||||
std::cout << "Test 6 PASS: Elisp multiple functions parsed" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 7: Python function with multiple parameters ---
|
||||
{
|
||||
std::string source = "def add(a, b):\n return a + b\n";
|
||||
auto module = TreeSitterParser::parsePython(source);
|
||||
|
||||
auto functions = module->getChildren("functions");
|
||||
assert(!functions.empty());
|
||||
auto* fn = static_cast<Function*>(functions[0]);
|
||||
|
||||
auto params = fn->getChildren("parameters");
|
||||
assert(params.size() == 2 && "Function should have exactly 2 parameters");
|
||||
|
||||
auto* p1 = static_cast<Parameter*>(params[0]);
|
||||
auto* p2 = static_cast<Parameter*>(params[1]);
|
||||
assert(p1->name == "a" && "First parameter should be 'a'");
|
||||
assert(p2->name == "b" && "Second parameter should be 'b'");
|
||||
|
||||
std::cout << "Test 7 PASS: Python multiple parameters parsed" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 8: C++ function with string literal ---
|
||||
{
|
||||
std::string source =
|
||||
"void greet() {\n"
|
||||
" return \"hello\";\n"
|
||||
"}\n";
|
||||
auto module = TreeSitterParser::parseCpp(source);
|
||||
|
||||
auto functions = module->getChildren("functions");
|
||||
assert(!functions.empty());
|
||||
auto* fn = static_cast<Function*>(functions[0]);
|
||||
assert(fn->name == "greet");
|
||||
|
||||
auto body = fn->getChildren("body");
|
||||
assert(!body.empty() && "Body should not be empty");
|
||||
|
||||
// Find a Return statement with a StringLiteral value
|
||||
bool hasStringLit = false;
|
||||
for (auto* stmt : body) {
|
||||
if (stmt->conceptType == "Return") {
|
||||
auto* ret = static_cast<Return*>(stmt);
|
||||
auto* val = ret->getChild("value");
|
||||
if (val && val->conceptType == "StringLiteral") {
|
||||
hasStringLit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(hasStringLit && "Should contain a Return with StringLiteral");
|
||||
|
||||
std::cout << "Test 8 PASS: C++ string literal parsed" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Summary ---
|
||||
std::cout << "\n=== Step 48 Results: " << passed << " passed, " << failed << " failed ===" << std::endl;
|
||||
return failed > 0 ? 1 : 0;
|
||||
}
|
||||
140
editor/tests/step49_test.cpp
Normal file
140
editor/tests/step49_test.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
// Step 49 TDD Test: Error Recovery and Diagnostics
|
||||
//
|
||||
// Tests the ParseResult and ParseDiagnostic types:
|
||||
// 1. Empty source → empty module, no errors
|
||||
// 2. Valid Python → module with functions, no diagnostics
|
||||
// 3. Python syntax error → module + diagnostics with line/column
|
||||
// 4. Partial parse (valid + broken function) → first function + diagnostics
|
||||
// 5. C++ syntax error → diagnostics present
|
||||
// 6. Elisp unbalanced parens → diagnostics present
|
||||
// 7. ParseResult::hasErrors() returns correct boolean
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include "ast/Parser.h"
|
||||
#include "ast/Annotation.h"
|
||||
|
||||
int main() {
|
||||
int passed = 0;
|
||||
int failed = 0;
|
||||
|
||||
// --- Test 1: Empty source → empty module, no errors ---
|
||||
{
|
||||
std::string source = "";
|
||||
auto result = TreeSitterParser::parsePythonWithDiagnostics(source);
|
||||
|
||||
assert(result.module != nullptr && "Module should not be null");
|
||||
assert(result.module->targetLanguage == "python");
|
||||
assert(result.module->getChildren("functions").empty() &&
|
||||
"Empty source should have no functions");
|
||||
assert(result.diagnostics.empty() &&
|
||||
"Empty source should have no diagnostics");
|
||||
assert(!result.hasErrors() && "Empty source should have no errors");
|
||||
|
||||
std::cout << "Test 1 PASS: Empty source → empty module, no errors" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 2: Valid Python → module with functions, no diagnostics ---
|
||||
{
|
||||
std::string source = "def f(x):\n return x + 1\n";
|
||||
auto result = TreeSitterParser::parsePythonWithDiagnostics(source);
|
||||
|
||||
assert(result.module != nullptr);
|
||||
auto functions = result.module->getChildren("functions");
|
||||
assert(!functions.empty() && "Should have parsed function");
|
||||
assert(result.diagnostics.empty() &&
|
||||
"Valid source should have no diagnostics");
|
||||
assert(!result.hasErrors());
|
||||
|
||||
std::cout << "Test 2 PASS: Valid Python → module with functions, no diagnostics" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 3: Python syntax error → diagnostics with line/column ---
|
||||
{
|
||||
std::string source = "def f(\n";
|
||||
auto result = TreeSitterParser::parsePythonWithDiagnostics(source);
|
||||
|
||||
assert(result.module != nullptr && "Module should still be created");
|
||||
assert(!result.diagnostics.empty() &&
|
||||
"Syntax error should produce diagnostics");
|
||||
assert(result.diagnostics[0].severity == "error");
|
||||
assert(result.diagnostics[0].line >= 1 && "Line should be >= 1");
|
||||
assert(result.diagnostics[0].column >= 1 && "Column should be >= 1");
|
||||
|
||||
std::cout << "Test 3 PASS: Python syntax error → diagnostics" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 4: Partial parse (valid + broken) → first function + diagnostics ---
|
||||
{
|
||||
std::string source =
|
||||
"def good(x):\n"
|
||||
" return x\n"
|
||||
"\n"
|
||||
"def bad(\n";
|
||||
auto result = TreeSitterParser::parsePythonWithDiagnostics(source);
|
||||
|
||||
assert(result.module != nullptr);
|
||||
auto functions = result.module->getChildren("functions");
|
||||
assert(!functions.empty() &&
|
||||
"Should have parsed at least the first valid function");
|
||||
auto* fn = static_cast<Function*>(functions[0]);
|
||||
assert(fn->name == "good" && "First function should be 'good'");
|
||||
|
||||
assert(!result.diagnostics.empty() &&
|
||||
"Broken second function should produce diagnostics");
|
||||
|
||||
std::cout << "Test 4 PASS: Partial parse → first function + diagnostics" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 5: C++ syntax error → diagnostics present ---
|
||||
{
|
||||
std::string source = "int f(int x { return x; }";
|
||||
auto result = TreeSitterParser::parseCppWithDiagnostics(source);
|
||||
|
||||
assert(result.module != nullptr);
|
||||
assert(!result.diagnostics.empty() &&
|
||||
"C++ syntax error should produce diagnostics");
|
||||
|
||||
std::cout << "Test 5 PASS: C++ syntax error → diagnostics present" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 6: Elisp unbalanced parens → diagnostics present ---
|
||||
{
|
||||
std::string source = "(defun f (x) (+ x 1)";
|
||||
auto result = TreeSitterParser::parseElispWithDiagnostics(source);
|
||||
|
||||
assert(result.module != nullptr);
|
||||
assert(!result.diagnostics.empty() &&
|
||||
"Unbalanced parens should produce diagnostics");
|
||||
|
||||
std::cout << "Test 6 PASS: Elisp unbalanced parens → diagnostics present" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 7: ParseResult::hasErrors() returns correct boolean ---
|
||||
{
|
||||
// Valid source
|
||||
auto goodResult = TreeSitterParser::parsePythonWithDiagnostics(
|
||||
"def f(x):\n return x\n");
|
||||
assert(!goodResult.hasErrors() && "Valid code should not have errors");
|
||||
|
||||
// Invalid source
|
||||
auto badResult = TreeSitterParser::parsePythonWithDiagnostics(
|
||||
"def f(\n");
|
||||
assert(badResult.hasErrors() && "Invalid code should have errors");
|
||||
|
||||
std::cout << "Test 7 PASS: hasErrors() returns correct boolean" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Summary ---
|
||||
std::cout << "\n=== Step 49 Results: " << passed << " passed, " << failed << " failed ===" << std::endl;
|
||||
return failed > 0 ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user