From d3e26d9216d752b5b5d8284f0037ca4e39a627fb Mon Sep 17 00:00:00 2001 From: Bill Date: Sat, 14 Feb 2026 17:11:00 +0000 Subject: [PATCH] =?UTF-8?q?Step=20304:=20Serialization=20+=20dispatch=20fo?= =?UTF-8?q?r=209=20new=20AST=20node=20types=20=E2=80=94=20Python=20+=20C++?= =?UTF-8?q?=20generators=20(12/12=20tests)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- editor/CMakeLists.txt | 4 + editor/src/ast/CppGeneratorTypes.h | 124 +++++++++++++ editor/src/ast/PythonGenerator.h | 142 +++++++++++++++ editor/tests/step304_test.cpp | 269 +++++++++++++++++++++++++++++ progress.md | 22 +++ 5 files changed, 561 insertions(+) create mode 100644 editor/tests/step304_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index fcfbd47..3d2d7d6 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -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) diff --git a/editor/src/ast/CppGeneratorTypes.h b/editor/src/ast/CppGeneratorTypes.h index fda149f..2747de6 100644 --- a/editor/src/ast/CppGeneratorTypes.h +++ b/editor/src/ast/CppGeneratorTypes.h @@ -103,6 +103,130 @@ return type->typeName; } + // --- New AST node visitors (Step 304) --- + + std::string visitClassDeclaration(const ASTNode* node) override { + auto* cls = static_cast(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(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(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(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(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(node); + return tp->name; + } + + std::string visitAsyncFunction(const ASTNode* node) override { + auto* af = static_cast(node); + std::ostringstream oss; + auto annotations = af->getChildren("annotations"); + for (const auto* a : annotations) oss << generate(a) << "\n"; + oss << "std::future " << af->name << "("; + auto params = af->getChildren("parameters"); + for (size_t i = 0; i < params.size(); ++i) { + if (i > 0) oss << ", "; + oss << visitParameter(static_cast(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(node); + auto* expr = aw->getChild("expression"); + return "co_await " + (expr ? generate(expr) : "/* missing */"); + } + + std::string visitLambdaExpression(const ASTNode* node) override { + auto* lam = static_cast(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(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(node); + return "// @" + dec->name; + } + std::string visitDerefStrategy(const DerefStrategy* annotation) override { // Generate C++-style comment for deref strategy if (annotation->strategy == "batched") { diff --git a/editor/src/ast/PythonGenerator.h b/editor/src/ast/PythonGenerator.h index 9148bc9..1142ff9 100644 --- a/editor/src/ast/PythonGenerator.h +++ b/editor/src/ast/PythonGenerator.h @@ -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(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(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(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(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(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(node); + return tp->name; + } + + std::string visitAsyncFunction(const ASTNode* node) override { + auto* af = static_cast(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(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(node); + auto* expr = aw->getChild("expression"); + return "await " + (expr ? generate(expr) : "None"); + } + + std::string visitLambdaExpression(const ASTNode* node) override { + auto* lam = static_cast(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(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(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 + ")"; } diff --git a/editor/tests/step304_test.cpp b/editor/tests/step304_test.cpp new file mode 100644 index 0000000..d613db4 --- /dev/null +++ b/editor/tests/step304_test.cpp @@ -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 +#include +#include +#include + +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("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("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("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("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" +void test_generic_type_dispatch() { + TEST(generic_type_dispatch); + auto gen = std::make_unique("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 test_async_function_dispatch() { + TEST(async_function_dispatch); + auto af = std::make_unique("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("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("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("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("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("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("c1", "Foo"); + auto iface = std::make_unique("i1", "Bar"); + auto meth = std::make_unique("m1", "baz"); + auto gen = std::make_unique("g1", "Box"); + gen->addChild("typeParameters", new TypeParameter("tp1", "T")); + auto tp = std::make_unique("tp2", "U"); + auto af = std::make_unique("af1", "load"); + auto aw = std::make_unique("aw1"); + aw->setChild("expression", []{ auto* c = new FunctionCall(); c->id = "fc1"; c->functionName = "f"; return c; }()); + auto lam = std::make_unique("l1"); + lam->captureList = {"z"}; + auto dec = std::make_unique("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; +} diff --git a/progress.md b/progress.md index bfb1e88..2fba412 100644 --- a/progress.md +++ b/progress.md @@ -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`, + async→`std::future 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+