Step 304: Serialization + dispatch for 9 new AST node types — Python + C++ generators (12/12 tests)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bill
2026-02-14 17:11:00 +00:00
parent 15230df621
commit d3e26d9216
5 changed files with 561 additions and 0 deletions

View File

@@ -1817,4 +1817,8 @@ add_executable(step303_test tests/step303_test.cpp)
target_include_directories(step303_test PRIVATE src)
target_link_libraries(step303_test PRIVATE nlohmann_json::nlohmann_json)
add_executable(step304_test tests/step304_test.cpp)
target_include_directories(step304_test PRIVATE src)
target_link_libraries(step304_test PRIVATE nlohmann_json::nlohmann_json)
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)

View File

@@ -103,6 +103,130 @@
return type->typeName;
}
// --- New AST node visitors (Step 304) ---
std::string visitClassDeclaration(const ASTNode* node) override {
auto* cls = static_cast<const ClassDeclaration*>(node);
std::ostringstream oss;
auto annotations = cls->getChildren("annotations");
for (const auto* a : annotations) oss << generate(a) << "\n";
oss << "class " << cls->name;
if (!cls->superClass.empty()) oss << " : public " << cls->superClass;
oss << " {\npublic:\n";
auto fields = cls->getChildren("fields");
for (const auto* f : fields)
oss << " " << generate(f) << ";\n";
auto methods = cls->getChildren("methods");
for (const auto* m : methods) oss << generate(m);
oss << "};\n";
return oss.str();
}
std::string visitInterfaceDeclaration(const ASTNode* node) override {
auto* iface = static_cast<const InterfaceDeclaration*>(node);
std::ostringstream oss;
oss << "class " << iface->name << " {\npublic:\n";
auto methods = iface->getChildren("methods");
for (const auto* m : methods) oss << generate(m);
oss << "};\n";
return oss.str();
}
std::string visitMethodDeclaration(const ASTNode* node) override {
auto* meth = static_cast<const MethodDeclaration*>(node);
std::ostringstream oss;
auto annotations = meth->getChildren("annotations");
for (const auto* a : annotations) oss << " " << generate(a) << "\n";
oss << " ";
if (meth->isStatic) oss << "static ";
if (meth->isVirtual) oss << "virtual ";
oss << "void " << meth->name << "(";
auto params = meth->getChildren("parameters");
for (size_t i = 0; i < params.size(); ++i) {
if (i > 0) oss << ", ";
oss << visitParameter(static_cast<const Parameter*>(params[i]));
}
oss << ")";
if (meth->isOverride) oss << " override";
auto body = meth->getChildren("body");
if (body.empty()) {
oss << " {}\n";
} else {
oss << " {\n";
for (const auto* s : body) oss << " " << generate(s) << "\n";
oss << " }\n";
}
return oss.str();
}
std::string visitGenericType(const ASTNode* node) override {
auto* gen = static_cast<const GenericType*>(node);
std::ostringstream oss;
oss << gen->baseName << "<";
auto params = gen->getChildren("typeParameters");
for (size_t i = 0; i < params.size(); ++i) {
if (i > 0) oss << ", ";
oss << generate(params[i]);
}
oss << ">";
return oss.str();
}
std::string visitTypeParameter(const ASTNode* node) override {
auto* tp = static_cast<const TypeParameter*>(node);
return tp->name;
}
std::string visitAsyncFunction(const ASTNode* node) override {
auto* af = static_cast<const AsyncFunction*>(node);
std::ostringstream oss;
auto annotations = af->getChildren("annotations");
for (const auto* a : annotations) oss << generate(a) << "\n";
oss << "std::future<void> " << af->name << "(";
auto params = af->getChildren("parameters");
for (size_t i = 0; i < params.size(); ++i) {
if (i > 0) oss << ", ";
oss << visitParameter(static_cast<const Parameter*>(params[i]));
}
oss << ") {\n";
auto body = af->getChildren("body");
for (const auto* s : body) oss << " " << generate(s) << "\n";
oss << "}\n";
return oss.str();
}
std::string visitAwaitExpression(const ASTNode* node) override {
auto* aw = static_cast<const AwaitExpression*>(node);
auto* expr = aw->getChild("expression");
return "co_await " + (expr ? generate(expr) : "/* missing */");
}
std::string visitLambdaExpression(const ASTNode* node) override {
auto* lam = static_cast<const LambdaExpression*>(node);
std::ostringstream oss;
oss << "[";
for (size_t i = 0; i < lam->captureList.size(); ++i) {
if (i > 0) oss << ", ";
oss << lam->captureList[i];
}
oss << "](";
auto params = lam->getChildren("parameters");
for (size_t i = 0; i < params.size(); ++i) {
if (i > 0) oss << ", ";
oss << "auto " << static_cast<const Parameter*>(params[i])->name;
}
oss << ") { ";
auto body = lam->getChildren("body");
for (const auto* s : body) oss << generate(s) << " ";
oss << "}";
return oss.str();
}
std::string visitDecoratorAnnotation(const ASTNode* node) override {
auto* dec = static_cast<const DecoratorAnnotation*>(node);
return "// @" + dec->name;
}
std::string visitDerefStrategy(const DerefStrategy* annotation) override {
// Generate C++-style comment for deref strategy
if (annotation->strategy == "batched") {

View File

@@ -530,6 +530,148 @@ public:
return type->typeName;
}
// --- New AST node visitors (Step 304) ---
std::string visitClassDeclaration(const ASTNode* node) override {
auto* cls = static_cast<const ClassDeclaration*>(node);
std::ostringstream oss;
auto annotations = cls->getChildren("annotations");
for (const auto* a : annotations) oss << generate(a) << "\n";
oss << "class " << cls->name;
if (!cls->superClass.empty()) oss << "(" << cls->superClass << ")";
oss << ":\n";
auto fields = cls->getChildren("fields");
auto methods = cls->getChildren("methods");
if (fields.empty() && methods.empty()) {
oss << " pass\n";
} else {
for (const auto* f : fields)
oss << " " << generate(f) << "\n";
for (const auto* m : methods)
oss << generate(m);
}
return oss.str();
}
std::string visitInterfaceDeclaration(const ASTNode* node) override {
auto* iface = static_cast<const InterfaceDeclaration*>(node);
std::ostringstream oss;
oss << "class " << iface->name << "(ABC):\n";
auto methods = iface->getChildren("methods");
if (methods.empty()) {
oss << " pass\n";
} else {
for (const auto* m : methods) oss << generate(m);
}
return oss.str();
}
std::string visitMethodDeclaration(const ASTNode* node) override {
auto* meth = static_cast<const MethodDeclaration*>(node);
std::ostringstream oss;
auto annotations = meth->getChildren("annotations");
for (const auto* a : annotations) oss << " " << generate(a) << "\n";
if (meth->isStatic) oss << " @staticmethod\n";
oss << " def " << meth->name << "(";
if (!meth->isStatic) oss << "self";
auto params = meth->getChildren("parameters");
for (size_t i = 0; i < params.size(); ++i) {
if (!meth->isStatic || i > 0) oss << ", ";
oss << visitParameter(static_cast<const Parameter*>(params[i]));
}
oss << "):\n";
auto body = meth->getChildren("body");
if (body.empty()) {
oss << " pass\n";
} else {
for (const auto* s : body) oss << " " << generate(s) << "\n";
}
return oss.str();
}
std::string visitGenericType(const ASTNode* node) override {
auto* gen = static_cast<const GenericType*>(node);
std::ostringstream oss;
oss << gen->baseName << "[";
auto params = gen->getChildren("typeParameters");
for (size_t i = 0; i < params.size(); ++i) {
if (i > 0) oss << ", ";
oss << generate(params[i]);
}
oss << "]";
return oss.str();
}
std::string visitTypeParameter(const ASTNode* node) override {
auto* tp = static_cast<const TypeParameter*>(node);
return tp->name;
}
std::string visitAsyncFunction(const ASTNode* node) override {
auto* af = static_cast<const AsyncFunction*>(node);
std::ostringstream oss;
auto annotations = af->getChildren("annotations");
for (const auto* a : annotations) oss << generate(a) << "\n";
oss << "async def " << af->name << "(";
auto params = af->getChildren("parameters");
for (size_t i = 0; i < params.size(); ++i) {
if (i > 0) oss << ", ";
oss << visitParameter(static_cast<const Parameter*>(params[i]));
}
oss << "):\n";
auto body = af->getChildren("body");
if (body.empty()) {
oss << " pass\n";
} else {
for (const auto* s : body) {
std::string code = generate(s);
oss << " " << code << "\n";
}
}
return oss.str();
}
std::string visitAwaitExpression(const ASTNode* node) override {
auto* aw = static_cast<const AwaitExpression*>(node);
auto* expr = aw->getChild("expression");
return "await " + (expr ? generate(expr) : "None");
}
std::string visitLambdaExpression(const ASTNode* node) override {
auto* lam = static_cast<const LambdaExpression*>(node);
std::ostringstream oss;
oss << "lambda ";
auto params = lam->getChildren("parameters");
for (size_t i = 0; i < params.size(); ++i) {
if (i > 0) oss << ", ";
oss << visitParameter(static_cast<const Parameter*>(params[i]));
}
oss << ": ";
auto body = lam->getChildren("body");
if (!body.empty()) {
oss << generate(body[0]);
} else {
oss << "None";
}
return oss.str();
}
std::string visitDecoratorAnnotation(const ASTNode* node) override {
auto* dec = static_cast<const DecoratorAnnotation*>(node);
std::ostringstream oss;
oss << "@" << dec->name;
auto args = dec->getChildren("arguments");
if (!args.empty()) {
oss << "(";
for (size_t i = 0; i < args.size(); ++i) {
if (i > 0) oss << ", ";
oss << generate(args[i]);
}
oss << ")";
}
return oss.str();
}
std::string visitDerefStrategy(const DerefStrategy* annotation) override {
return "# @deref(" + annotation->strategy + ")";
}

View File

@@ -0,0 +1,269 @@
// Step 304: Serialization + Dispatch for New Nodes (12 tests)
// Tests that all 9 new AST node types (ClassDeclaration, InterfaceDeclaration,
// MethodDeclaration, GenericType, TypeParameter, AsyncFunction, AwaitExpression,
// LambdaExpression, DecoratorAnnotation) serialize/deserialize correctly and
// dispatch through PythonGenerator and CppGenerator with non-empty output.
#include "ast/ClassDeclaration.h"
#include "ast/GenericType.h"
#include "ast/AsyncNodes.h"
#include "ast/Serialization.h"
#include "ast/PythonGenerator.h"
#include "ast/CppGenerator.h"
#include "ast/Module.h"
#include "ast/Function.h"
#include "ast/Variable.h"
#include "ast/Statement.h"
#include "ast/Expression.h"
#include <nlohmann/json.hpp>
#include <iostream>
#include <string>
#include <memory>
using json = nlohmann::json;
static int passed = 0, failed = 0;
#define TEST(name) { std::cout << " " << #name << "... "; }
#define PASS() { std::cout << "PASS\n"; ++passed; }
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
// 1. ClassDeclaration dispatch — Python generates "class Name:"
void test_class_python_dispatch() {
TEST(class_python_dispatch);
auto cls = std::make_unique<ClassDeclaration>("cls1", "Animal");
cls->superClass = "LivingThing";
PythonGenerator pyGen;
std::string out = pyGen.generate(cls.get());
CHECK(!out.empty(), "output should not be empty");
CHECK(out.find("class Animal") != std::string::npos, "should contain 'class Animal'");
CHECK(out.find("LivingThing") != std::string::npos, "should contain superclass");
PASS();
}
// 2. ClassDeclaration dispatch — C++ generates "class Name : public Super {"
void test_class_cpp_dispatch() {
TEST(class_cpp_dispatch);
auto cls = std::make_unique<ClassDeclaration>("cls1", "Animal");
cls->superClass = "LivingThing";
CppGenerator cppGen;
std::string out = cppGen.generate(cls.get());
CHECK(!out.empty(), "output should not be empty");
CHECK(out.find("class Animal") != std::string::npos, "should contain 'class Animal'");
CHECK(out.find("public LivingThing") != std::string::npos, "should contain ': public LivingThing'");
PASS();
}
// 3. InterfaceDeclaration dispatch — both generators produce output
void test_interface_dispatch() {
TEST(interface_dispatch);
auto iface = std::make_unique<InterfaceDeclaration>("if1", "Drawable");
PythonGenerator pyGen;
CppGenerator cppGen;
std::string pyOut = pyGen.generate(iface.get());
std::string cppOut = cppGen.generate(iface.get());
CHECK(!pyOut.empty(), "Python output should not be empty");
CHECK(!cppOut.empty(), "C++ output should not be empty");
CHECK(pyOut.find("Drawable") != std::string::npos, "Python should contain name");
CHECK(cppOut.find("Drawable") != std::string::npos, "C++ should contain name");
PASS();
}
// 4. MethodDeclaration dispatch — Python generates "def name(self):"
void test_method_python_dispatch() {
TEST(method_python_dispatch);
auto meth = std::make_unique<MethodDeclaration>("m1", "draw");
meth->isStatic = false;
meth->isVirtual = true;
PythonGenerator pyGen;
std::string out = pyGen.generate(meth.get());
CHECK(!out.empty(), "output should not be empty");
CHECK(out.find("def draw") != std::string::npos, "should contain 'def draw'");
PASS();
}
// 5. GenericType dispatch — Python "Name[T]", C++ "Name<T>"
void test_generic_type_dispatch() {
TEST(generic_type_dispatch);
auto gen = std::make_unique<GenericType>("gt1", "Container");
auto tp = new TypeParameter("tp1", "T");
gen->addChild("typeParameters", tp);
PythonGenerator pyGen;
CppGenerator cppGen;
std::string pyOut = pyGen.generate(gen.get());
std::string cppOut = cppGen.generate(gen.get());
CHECK(pyOut.find("Container") != std::string::npos, "Python should contain 'Container'");
CHECK(pyOut.find("T") != std::string::npos, "Python should contain 'T'");
CHECK(cppOut.find("Container") != std::string::npos, "C++ should contain 'Container'");
CHECK(cppOut.find("<") != std::string::npos, "C++ should use angle brackets");
PASS();
}
// 6. AsyncFunction dispatch — Python "async def name():", C++ "std::future<void>"
void test_async_function_dispatch() {
TEST(async_function_dispatch);
auto af = std::make_unique<AsyncFunction>("af1", "fetchData");
PythonGenerator pyGen;
CppGenerator cppGen;
std::string pyOut = pyGen.generate(af.get());
std::string cppOut = cppGen.generate(af.get());
CHECK(pyOut.find("async def fetchData") != std::string::npos, "Python should have 'async def fetchData'");
CHECK(cppOut.find("std::future") != std::string::npos, "C++ should have 'std::future'");
CHECK(cppOut.find("fetchData") != std::string::npos, "C++ should have function name");
PASS();
}
// 7. AwaitExpression dispatch — Python "await expr", C++ "co_await expr"
void test_await_expression_dispatch() {
TEST(await_expression_dispatch);
auto aw = std::make_unique<AwaitExpression>("aw1");
auto call = []{ auto* c = new FunctionCall(); c->id = "fc1"; c->functionName = "getData"; return c; }();
aw->setChild("expression", call);
PythonGenerator pyGen;
CppGenerator cppGen;
std::string pyOut = pyGen.generate(aw.get());
std::string cppOut = cppGen.generate(aw.get());
CHECK(pyOut.find("await") != std::string::npos, "Python should contain 'await'");
CHECK(cppOut.find("co_await") != std::string::npos, "C++ should contain 'co_await'");
PASS();
}
// 8. LambdaExpression dispatch — Python "lambda", C++ "[captures](...)"
void test_lambda_dispatch() {
TEST(lambda_dispatch);
auto lam = std::make_unique<LambdaExpression>("lam1");
lam->captureList = {"x", "y"};
auto param = new Parameter();
param->id = "p1";
param->name = "item";
lam->addChild("parameters", param);
PythonGenerator pyGen;
CppGenerator cppGen;
std::string pyOut = pyGen.generate(lam.get());
std::string cppOut = cppGen.generate(lam.get());
CHECK(pyOut.find("lambda") != std::string::npos, "Python should contain 'lambda'");
CHECK(cppOut.find("[x, y]") != std::string::npos, "C++ should contain captures '[x, y]'");
CHECK(cppOut.find("item") != std::string::npos, "C++ should contain parameter name");
PASS();
}
// 9. DecoratorAnnotation dispatch — Python "@name", C++ "// @name"
void test_decorator_dispatch() {
TEST(decorator_dispatch);
auto dec = std::make_unique<DecoratorAnnotation>("dec1", "cache");
PythonGenerator pyGen;
CppGenerator cppGen;
std::string pyOut = pyGen.generate(dec.get());
std::string cppOut = cppGen.generate(dec.get());
CHECK(pyOut.find("@cache") != std::string::npos, "Python should contain '@cache'");
CHECK(cppOut.find("@cache") != std::string::npos, "C++ should contain '// @cache'");
PASS();
}
// 10. ClassDeclaration with methods — full nested dispatch
void test_class_with_methods_dispatch() {
TEST(class_with_methods_dispatch);
auto cls = std::make_unique<ClassDeclaration>("cls1", "Shape");
auto meth = new MethodDeclaration("m1", "area");
meth->isVirtual = true;
cls->addChild("methods", meth);
PythonGenerator pyGen;
std::string pyOut = pyGen.generate(cls.get());
CHECK(pyOut.find("class Shape") != std::string::npos, "should contain class name");
CHECK(pyOut.find("def area") != std::string::npos, "should contain method name");
PASS();
}
// 11. AsyncFunction with body containing AwaitExpression — nested dispatch
void test_async_with_await_dispatch() {
TEST(async_with_await_dispatch);
auto af = std::make_unique<AsyncFunction>("af1", "loadUser");
auto aw = new AwaitExpression("aw1");
aw->setChild("expression", []{ auto* c = new FunctionCall(); c->id = "fc1"; c->functionName = "httpGet"; return c; }());
auto exprStmt = new ExpressionStatement();
exprStmt->id = "es1";
exprStmt->setChild("expression", aw);
af->addChild("body", exprStmt);
CppGenerator cppGen;
std::string out = cppGen.generate(af.get());
CHECK(out.find("std::future") != std::string::npos, "should have std::future");
CHECK(out.find("loadUser") != std::string::npos, "should have function name");
PASS();
}
// 12. All 9 new node types roundtrip through JSON and re-dispatch identically
void test_all_new_nodes_json_roundtrip_dispatch() {
TEST(all_new_nodes_json_roundtrip_dispatch);
PythonGenerator pyGen;
// Build one of each type and verify roundtrip preserves dispatch output
auto cls = std::make_unique<ClassDeclaration>("c1", "Foo");
auto iface = std::make_unique<InterfaceDeclaration>("i1", "Bar");
auto meth = std::make_unique<MethodDeclaration>("m1", "baz");
auto gen = std::make_unique<GenericType>("g1", "Box");
gen->addChild("typeParameters", new TypeParameter("tp1", "T"));
auto tp = std::make_unique<TypeParameter>("tp2", "U");
auto af = std::make_unique<AsyncFunction>("af1", "load");
auto aw = std::make_unique<AwaitExpression>("aw1");
aw->setChild("expression", []{ auto* c = new FunctionCall(); c->id = "fc1"; c->functionName = "f"; return c; }());
auto lam = std::make_unique<LambdaExpression>("l1");
lam->captureList = {"z"};
auto dec = std::make_unique<DecoratorAnnotation>("d1", "test");
struct Case { const char* label; ASTNode* node; };
Case cases[] = {
{"ClassDeclaration", cls.get()},
{"InterfaceDeclaration", iface.get()},
{"MethodDeclaration", meth.get()},
{"GenericType", gen.get()},
{"TypeParameter", tp.get()},
{"AsyncFunction", af.get()},
{"AwaitExpression", aw.get()},
{"LambdaExpression", lam.get()},
{"DecoratorAnnotation", dec.get()},
};
for (auto& c : cases) {
std::string origOut = pyGen.generate(c.node);
CHECK(!origOut.empty(), std::string("empty output for ") + c.label);
json j = toJson(c.node);
ASTNode* restored = fromJson(j);
CHECK(restored != nullptr, std::string("null fromJson for ") + c.label);
CHECK(restored->conceptType == c.label, std::string("wrong type for ") + c.label);
std::string restoredOut = pyGen.generate(restored);
CHECK(origOut == restoredOut, std::string("roundtrip output mismatch for ") + c.label);
delete restored;
}
PASS();
}
int main() {
std::cout << "=== Step 304: Serialization + Dispatch for New Nodes ===\n";
test_class_python_dispatch();
test_class_cpp_dispatch();
test_interface_dispatch();
test_method_python_dispatch();
test_generic_type_dispatch();
test_async_function_dispatch();
test_await_expression_dispatch();
test_lambda_dispatch();
test_decorator_dispatch();
test_class_with_methods_dispatch();
test_async_with_await_dispatch();
test_all_new_nodes_json_roundtrip_dispatch();
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
return failed > 0 ? 1 : 0;
}

View File

@@ -1062,6 +1062,28 @@ and decorator annotations. Full JSON roundtrip via Serialization.h.
captureList array preservation, async+await nested in module roundtrip,
lambda with captures inside function body roundtrip
### Step 304: Serialization + Dispatch for New Nodes (12 tests)
**Status:** PASS (12/12 tests)
All 9 new AST node types (ClassDeclaration, InterfaceDeclaration, MethodDeclaration,
GenericType, TypeParameter, AsyncFunction, AwaitExpression, LambdaExpression,
DecoratorAnnotation) dispatch through PythonGenerator and CppGenerator with
language-appropriate output. JSON roundtrip preserves dispatch output identity.
**Key files:**
- `editor/src/ast/PythonGenerator.h` — 9 visitor implementations: class→`class Name(Super):`,
interface→`class Name(ABC):`, method→`def name(self):`, generic→`Name[T]`,
async→`async def name():`, await→`await expr`, lambda→`lambda x: body`,
decorator→`@name(args)`
- `editor/src/ast/CppGeneratorTypes.h` — 9 visitor implementations: class→`class Name : public Super {}`,
interface→`class Name {}`, method→`virtual void name()`, generic→`Name<T>`,
async→`std::future<void> name()`, await→`co_await expr`, lambda→`[captures](auto x) { body }`,
decorator→`// @name`
- `editor/src/ast/ProjectionGenerator.h` — dispatchGenerate branches for all 9 types (lines 322-339)
- `editor/tests/step304_test.cpp` — 12 tests: per-type Python+C++ dispatch (9 types),
nested class-with-methods dispatch, async-with-await nested dispatch,
all-9-types JSON roundtrip→dispatch identity check
---
# Roadmap Planning — Sprints 12-25+