From ca01fc67445308cc8a66d675c31195fb681a7910 Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 16 Feb 2026 00:38:34 -0700 Subject: [PATCH] Step 338: EnumDeclaration + NamespaceDeclaration (12/12 tests) Added EnumDeclaration, EnumMember, NamespaceDeclaration, TypeAlias AST nodes with full serialization roundtrip and CompactAST support. Enums support scoped/unscoped, underlying type, member values. Namespaces support nesting. Co-Authored-By: Claude Opus 4.6 --- editor/CMakeLists.txt | 4 + editor/src/CompactAST.h | 9 ++ editor/src/ast/EnumNamespaceNodes.h | 60 ++++++++ editor/src/ast/Serialization.h | 49 +++++++ editor/tests/step338_test.cpp | 209 ++++++++++++++++++++++++++++ progress.md | 21 +++ 6 files changed, 352 insertions(+) create mode 100644 editor/src/ast/EnumNamespaceNodes.h create mode 100644 editor/tests/step338_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 1386fe4..cef6cdf 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -2031,4 +2031,8 @@ add_executable(step337_test tests/step337_test.cpp) target_include_directories(step337_test PRIVATE src) target_link_libraries(step337_test PRIVATE nlohmann_json::nlohmann_json) +add_executable(step338_test tests/step338_test.cpp) +target_include_directories(step338_test PRIVATE src) +target_link_libraries(step338_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/CompactAST.h b/editor/src/CompactAST.h index 4126bee..bc8faae 100644 --- a/editor/src/CompactAST.h +++ b/editor/src/CompactAST.h @@ -431,6 +431,15 @@ inline std::string getNodeName(const ASTNode* node) { return static_cast(node)->directive; if (ct == "MacroDefinition") return static_cast(node)->name; + // Enum/Namespace/TypeAlias (Step 338) + if (ct == "EnumDeclaration") + return static_cast(node)->name; + if (ct == "EnumMember") + return static_cast(node)->name; + if (ct == "NamespaceDeclaration") + return static_cast(node)->name; + if (ct == "TypeAlias") + return static_cast(node)->aliasName; return ""; } diff --git a/editor/src/ast/EnumNamespaceNodes.h b/editor/src/ast/EnumNamespaceNodes.h new file mode 100644 index 0000000..0bcf3ff --- /dev/null +++ b/editor/src/ast/EnumNamespaceNodes.h @@ -0,0 +1,60 @@ +#pragma once +#include "ASTNode.h" +#include + +// Enum, namespace, and type alias AST nodes — statement-level. + +class EnumMember : public ASTNode { +public: + std::string name; + std::string value; // Optional explicit value ("1", "0xFF") + + EnumMember() { conceptType = "EnumMember"; } + EnumMember(const std::string& id, const std::string& n, const std::string& v = "") + : name(n), value(v) { + this->id = id; + conceptType = "EnumMember"; + } +}; + +class EnumDeclaration : public ASTNode { +public: + std::string name; + bool isScoped = false; // enum class (true) vs plain enum (false) + std::string underlyingType; // Optional: "int", "uint8_t", etc. + // Children role "members": vector of EnumMember nodes + + EnumDeclaration() { conceptType = "EnumDeclaration"; } + EnumDeclaration(const std::string& id, const std::string& n, bool scoped = false) + : name(n), isScoped(scoped) { + this->id = id; + conceptType = "EnumDeclaration"; + } +}; + +class NamespaceDeclaration : public ASTNode { +public: + std::string name; // Empty for anonymous namespaces + // Children role "body": statements/declarations inside + + NamespaceDeclaration() { conceptType = "NamespaceDeclaration"; } + NamespaceDeclaration(const std::string& id, const std::string& n) + : name(n) { + this->id = id; + conceptType = "NamespaceDeclaration"; + } +}; + +class TypeAlias : public ASTNode { +public: + std::string aliasName; // The new name + std::string targetType; // What it aliases + bool isUsing = true; // using (true) vs typedef (false) + + TypeAlias() { conceptType = "TypeAlias"; } + TypeAlias(const std::string& id, const std::string& alias, const std::string& target, bool usingStyle = true) + : aliasName(alias), targetType(target), isUsing(usingStyle) { + this->id = id; + conceptType = "TypeAlias"; + } +}; diff --git a/editor/src/ast/Serialization.h b/editor/src/ast/Serialization.h index ff0ef5e..0e0d714 100644 --- a/editor/src/ast/Serialization.h +++ b/editor/src/ast/Serialization.h @@ -18,6 +18,7 @@ #include "GenericType.h" #include "AsyncNodes.h" #include "PreprocessorNodes.h" +#include "EnumNamespaceNodes.h" using json = nlohmann::json; @@ -523,6 +524,28 @@ inline json propertiesToJson(const ASTNode* node) { if (n->isFunctionLike) props["isFunctionLike"] = n->isFunctionLike; if (!n->parameters.empty()) props["parameters"] = n->parameters; } + // Enum/Namespace/TypeAlias nodes (Step 338) + else if (ct == "EnumDeclaration") { + auto* n = static_cast(node); + props["name"] = n->name; + if (n->isScoped) props["isScoped"] = n->isScoped; + if (!n->underlyingType.empty()) props["underlyingType"] = n->underlyingType; + } + else if (ct == "EnumMember") { + auto* n = static_cast(node); + props["name"] = n->name; + if (!n->value.empty()) props["value"] = n->value; + } + else if (ct == "NamespaceDeclaration") { + auto* n = static_cast(node); + props["name"] = n->name; + } + else if (ct == "TypeAlias") { + auto* n = static_cast(node); + props["aliasName"] = n->aliasName; + props["targetType"] = n->targetType; + if (!n->isUsing) props["isUsing"] = n->isUsing; + } // NullLiteral, ListLiteral, IndexAccess, Block, Assignment, IfStatement, // WhileLoop, Return, ExpressionStatement, ListType, SetType, MapType, // TupleType, ArrayType, OptionalType — no extra properties @@ -689,6 +712,10 @@ inline ASTNode* createNode(const std::string& conceptName) { if (conceptName == "IncludeDirective") return new IncludeDirective(); if (conceptName == "PragmaDirective") return new PragmaDirective(); if (conceptName == "MacroDefinition") return new MacroDefinition(); + if (conceptName == "EnumDeclaration") return new EnumDeclaration(); + if (conceptName == "EnumMember") return new EnumMember(); + if (conceptName == "NamespaceDeclaration") return new NamespaceDeclaration(); + if (conceptName == "TypeAlias") return new TypeAlias(); return nullptr; } @@ -1230,6 +1257,28 @@ inline void setPropertiesFromJson(ASTNode* node, const json& props) { if (p.is_string()) n->parameters.push_back(p.get()); } } + // Enum/Namespace/TypeAlias nodes (Step 338) + else if (ct == "EnumDeclaration") { + auto* n = static_cast(node); + if (props.contains("name")) n->name = props["name"].get(); + if (props.contains("isScoped")) n->isScoped = props["isScoped"].get(); + if (props.contains("underlyingType")) n->underlyingType = props["underlyingType"].get(); + } + else if (ct == "EnumMember") { + auto* n = static_cast(node); + if (props.contains("name")) n->name = props["name"].get(); + if (props.contains("value")) n->value = props["value"].get(); + } + else if (ct == "NamespaceDeclaration") { + auto* n = static_cast(node); + if (props.contains("name")) n->name = props["name"].get(); + } + else if (ct == "TypeAlias") { + auto* n = static_cast(node); + if (props.contains("aliasName")) n->aliasName = props["aliasName"].get(); + if (props.contains("targetType")) n->targetType = props["targetType"].get(); + if (props.contains("isUsing")) n->isUsing = props["isUsing"].get(); + } } inline std::string generateNodeId() { diff --git a/editor/tests/step338_test.cpp b/editor/tests/step338_test.cpp new file mode 100644 index 0000000..e5a3b7a --- /dev/null +++ b/editor/tests/step338_test.cpp @@ -0,0 +1,209 @@ +// Step 338: EnumDeclaration + NamespaceDeclaration (12 tests) +// Tests EnumDeclaration, EnumMember, NamespaceDeclaration, TypeAlias construction, +// serialization roundtrip, and CompactAST output. + +#include +#include +#include +#include "ast/EnumNamespaceNodes.h" +#include "ast/PreprocessorNodes.h" +#include "ast/AsyncNodes.h" +#include "ast/Serialization.h" +#include "CompactAST.h" +#include "ast/Function.h" + +int main() { + int passed = 0; + + // Test 1: EnumDeclaration construction — scoped enum + { + auto* e = new EnumDeclaration("e1", "Color", true); + assert(e->conceptType == "EnumDeclaration"); + assert(e->name == "Color"); + assert(e->isScoped); + assert(e->underlyingType.empty()); + delete e; + std::cout << "Test 1 PASSED: EnumDeclaration scoped\n"; + passed++; + } + + // Test 2: EnumDeclaration — unscoped with underlying type + { + auto* e = new EnumDeclaration("e2", "Flags", false); + e->underlyingType = "uint8_t"; + assert(!e->isScoped); + assert(e->underlyingType == "uint8_t"); + delete e; + std::cout << "Test 2 PASSED: EnumDeclaration unscoped with underlying type\n"; + passed++; + } + + // Test 3: EnumMember with explicit value + { + auto* m = new EnumMember("m1", "Red", "0"); + assert(m->conceptType == "EnumMember"); + assert(m->name == "Red"); + assert(m->value == "0"); + auto* m2 = new EnumMember("m2", "Green"); + assert(m2->value.empty()); + delete m; delete m2; + std::cout << "Test 3 PASSED: EnumMember with explicit value\n"; + passed++; + } + + // Test 4: Enum with 4 members as children + { + auto* e = new EnumDeclaration("e3", "Direction", true); + e->addChild("members", new EnumMember("m3", "North", "0")); + e->addChild("members", new EnumMember("m4", "South", "1")); + e->addChild("members", new EnumMember("m5", "East", "2")); + e->addChild("members", new EnumMember("m6", "West", "3")); + auto members = e->getChildren("members"); + assert(members.size() == 4); + assert(static_cast(members[2])->name == "East"); + delete e; + std::cout << "Test 4 PASSED: Enum with 4 members as children\n"; + passed++; + } + + // Test 5: NamespaceDeclaration with body children + { + auto* ns = new NamespaceDeclaration("ns1", "whetstone"); + auto* fn = new Function("fn1", "helper"); + ns->addChild("body", fn); + auto body = ns->getChildren("body"); + assert(body.size() == 1); + assert(body[0]->conceptType == "Function"); + assert(ns->name == "whetstone"); + delete ns; + std::cout << "Test 5 PASSED: NamespaceDeclaration with body\n"; + passed++; + } + + // Test 6: TypeAlias — using vs typedef + { + auto* ua = new TypeAlias("ta1", "StringVec", "std::vector", true); + assert(ua->conceptType == "TypeAlias"); + assert(ua->aliasName == "StringVec"); + assert(ua->targetType == "std::vector"); + assert(ua->isUsing); + + auto* td = new TypeAlias("ta2", "UINT", "unsigned int", false); + assert(!td->isUsing); + delete ua; delete td; + std::cout << "Test 6 PASSED: TypeAlias using vs typedef\n"; + passed++; + } + + // Test 7: EnumDeclaration JSON roundtrip + { + auto* e = new EnumDeclaration("e4", "Status", true); + e->underlyingType = "int"; + e->addChild("members", new EnumMember("m7", "OK", "0")); + e->addChild("members", new EnumMember("m8", "Error", "1")); + json j = toJson(e); + auto* restored = fromJson(j); + assert(restored->conceptType == "EnumDeclaration"); + auto* re = static_cast(restored); + assert(re->name == "Status"); + assert(re->isScoped); + assert(re->underlyingType == "int"); + auto members = re->getChildren("members"); + assert(members.size() == 2); + assert(static_cast(members[1])->name == "Error"); + assert(static_cast(members[1])->value == "1"); + delete e; delete restored; + std::cout << "Test 7 PASSED: EnumDeclaration JSON roundtrip\n"; + passed++; + } + + // Test 8: NamespaceDeclaration JSON roundtrip + { + auto* ns = new NamespaceDeclaration("ns2", "detail"); + ns->addChild("body", new Function("fn2", "impl")); + json j = toJson(ns); + auto* restored = fromJson(j); + assert(restored->conceptType == "NamespaceDeclaration"); + auto* rns = static_cast(restored); + assert(rns->name == "detail"); + auto body = rns->getChildren("body"); + assert(body.size() == 1); + delete ns; delete restored; + std::cout << "Test 8 PASSED: NamespaceDeclaration JSON roundtrip\n"; + passed++; + } + + // Test 9: TypeAlias JSON roundtrip + { + auto* ta = new TypeAlias("ta3", "Vec3", "std::array", true); + json j = toJson(ta); + auto* restored = fromJson(j); + assert(restored->conceptType == "TypeAlias"); + auto* rta = static_cast(restored); + assert(rta->aliasName == "Vec3"); + assert(rta->targetType == "std::array"); + assert(rta->isUsing); + delete ta; delete restored; + std::cout << "Test 9 PASSED: TypeAlias JSON roundtrip\n"; + passed++; + } + + // Test 10: CompactAST node names + { + auto* e = new EnumDeclaration("e5", "Fruit", true); + assert(getNodeName(e) == "Fruit"); + auto* m = new EnumMember("m9", "Apple"); + assert(getNodeName(m) == "Apple"); + auto* ns = new NamespaceDeclaration("ns3", "ast"); + assert(getNodeName(ns) == "ast"); + auto* ta = new TypeAlias("ta4", "Ptr", "std::shared_ptr", true); + assert(getNodeName(ta) == "Ptr"); + delete e; delete m; delete ns; delete ta; + std::cout << "Test 10 PASSED: CompactAST node names\n"; + passed++; + } + + // Test 11: Nested namespace (namespace inside namespace) + { + auto* outer = new NamespaceDeclaration("ns4", "whetstone"); + auto* inner = new NamespaceDeclaration("ns5", "detail"); + auto* fn = new Function("fn3", "helper"); + inner->addChild("body", fn); + outer->addChild("body", inner); + auto outerBody = outer->getChildren("body"); + assert(outerBody.size() == 1); + assert(outerBody[0]->conceptType == "NamespaceDeclaration"); + auto innerBody = outerBody[0]->getChildren("body"); + assert(innerBody.size() == 1); + assert(innerBody[0]->conceptType == "Function"); + delete outer; + std::cout << "Test 11 PASSED: Nested namespace\n"; + passed++; + } + + // Test 12: Enum inside namespace + { + auto* ns = new NamespaceDeclaration("ns6", "types"); + auto* e = new EnumDeclaration("e6", "Color", true); + e->addChild("members", new EnumMember("m10", "Red")); + e->addChild("members", new EnumMember("m11", "Blue")); + ns->addChild("body", e); + auto body = ns->getChildren("body"); + assert(body.size() == 1); + assert(body[0]->conceptType == "EnumDeclaration"); + auto* re = static_cast(body[0]); + assert(re->getChildren("members").size() == 2); + // JSON roundtrip of the whole structure + json j = toJson(ns); + auto* restored = fromJson(j); + auto rbody = restored->getChildren("body"); + assert(rbody.size() == 1); + assert(rbody[0]->conceptType == "EnumDeclaration"); + delete ns; delete restored; + std::cout << "Test 12 PASSED: Enum inside namespace\n"; + passed++; + } + + std::cout << "\nResults: " << passed << "/12 tests passed\n"; + return (passed == 12) ? 0 : 1; +} diff --git a/progress.md b/progress.md index 02a488e..6a8c857 100644 --- a/progress.md +++ b/progress.md @@ -1584,6 +1584,27 @@ serialization roundtrip and CompactAST node name support. MacroDefinition - `editor/CMakeLists.txt` — step337_test target +### Step 338: EnumDeclaration + NamespaceDeclaration +**Status:** PASS (12/12 tests) + +Added EnumDeclaration (scoped/unscoped, underlying type, EnumMember children), +NamespaceDeclaration (with body children, supports nesting), and TypeAlias (using +vs typedef). Full JSON serialization roundtrip and CompactAST names for all 4 types. + +**Files created:** +- `editor/src/ast/EnumNamespaceNodes.h` — EnumDeclaration, EnumMember, + NamespaceDeclaration, TypeAlias +- `editor/tests/step338_test.cpp` — 12 tests: construction, scoped vs unscoped enum, + members with values, namespace with body, typedef vs using, JSON roundtrip for all + 4 types, CompactAST names, nested namespace, enum inside namespace + +**Files modified:** +- `editor/src/ast/Serialization.h` — propertiesToJson/createNode/setPropertiesFromJson + for all 4 types +- `editor/src/CompactAST.h` — getNodeName for EnumDeclaration, EnumMember, + NamespaceDeclaration, TypeAlias +- `editor/CMakeLists.txt` — step338_test target + --- # Roadmap Planning — Sprints 12-25+