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 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -431,6 +431,15 @@ inline std::string getNodeName(const ASTNode* node) {
|
||||
return static_cast<const PragmaDirective*>(node)->directive;
|
||||
if (ct == "MacroDefinition")
|
||||
return static_cast<const MacroDefinition*>(node)->name;
|
||||
// Enum/Namespace/TypeAlias (Step 338)
|
||||
if (ct == "EnumDeclaration")
|
||||
return static_cast<const EnumDeclaration*>(node)->name;
|
||||
if (ct == "EnumMember")
|
||||
return static_cast<const EnumMember*>(node)->name;
|
||||
if (ct == "NamespaceDeclaration")
|
||||
return static_cast<const NamespaceDeclaration*>(node)->name;
|
||||
if (ct == "TypeAlias")
|
||||
return static_cast<const TypeAlias*>(node)->aliasName;
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
60
editor/src/ast/EnumNamespaceNodes.h
Normal file
60
editor/src/ast/EnumNamespaceNodes.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
#include "ASTNode.h"
|
||||
#include <string>
|
||||
|
||||
// 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";
|
||||
}
|
||||
};
|
||||
@@ -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<const EnumDeclaration*>(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<const EnumMember*>(node);
|
||||
props["name"] = n->name;
|
||||
if (!n->value.empty()) props["value"] = n->value;
|
||||
}
|
||||
else if (ct == "NamespaceDeclaration") {
|
||||
auto* n = static_cast<const NamespaceDeclaration*>(node);
|
||||
props["name"] = n->name;
|
||||
}
|
||||
else if (ct == "TypeAlias") {
|
||||
auto* n = static_cast<const TypeAlias*>(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<std::string>());
|
||||
}
|
||||
}
|
||||
// Enum/Namespace/TypeAlias nodes (Step 338)
|
||||
else if (ct == "EnumDeclaration") {
|
||||
auto* n = static_cast<EnumDeclaration*>(node);
|
||||
if (props.contains("name")) n->name = props["name"].get<std::string>();
|
||||
if (props.contains("isScoped")) n->isScoped = props["isScoped"].get<bool>();
|
||||
if (props.contains("underlyingType")) n->underlyingType = props["underlyingType"].get<std::string>();
|
||||
}
|
||||
else if (ct == "EnumMember") {
|
||||
auto* n = static_cast<EnumMember*>(node);
|
||||
if (props.contains("name")) n->name = props["name"].get<std::string>();
|
||||
if (props.contains("value")) n->value = props["value"].get<std::string>();
|
||||
}
|
||||
else if (ct == "NamespaceDeclaration") {
|
||||
auto* n = static_cast<NamespaceDeclaration*>(node);
|
||||
if (props.contains("name")) n->name = props["name"].get<std::string>();
|
||||
}
|
||||
else if (ct == "TypeAlias") {
|
||||
auto* n = static_cast<TypeAlias*>(node);
|
||||
if (props.contains("aliasName")) n->aliasName = props["aliasName"].get<std::string>();
|
||||
if (props.contains("targetType")) n->targetType = props["targetType"].get<std::string>();
|
||||
if (props.contains("isUsing")) n->isUsing = props["isUsing"].get<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string generateNodeId() {
|
||||
|
||||
209
editor/tests/step338_test.cpp
Normal file
209
editor/tests/step338_test.cpp
Normal file
@@ -0,0 +1,209 @@
|
||||
// Step 338: EnumDeclaration + NamespaceDeclaration (12 tests)
|
||||
// Tests EnumDeclaration, EnumMember, NamespaceDeclaration, TypeAlias construction,
|
||||
// serialization roundtrip, and CompactAST output.
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#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<EnumMember*>(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<std::string>", true);
|
||||
assert(ua->conceptType == "TypeAlias");
|
||||
assert(ua->aliasName == "StringVec");
|
||||
assert(ua->targetType == "std::vector<std::string>");
|
||||
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<EnumDeclaration*>(restored);
|
||||
assert(re->name == "Status");
|
||||
assert(re->isScoped);
|
||||
assert(re->underlyingType == "int");
|
||||
auto members = re->getChildren("members");
|
||||
assert(members.size() == 2);
|
||||
assert(static_cast<EnumMember*>(members[1])->name == "Error");
|
||||
assert(static_cast<EnumMember*>(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<NamespaceDeclaration*>(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<float, 3>", true);
|
||||
json j = toJson(ta);
|
||||
auto* restored = fromJson(j);
|
||||
assert(restored->conceptType == "TypeAlias");
|
||||
auto* rta = static_cast<TypeAlias*>(restored);
|
||||
assert(rta->aliasName == "Vec3");
|
||||
assert(rta->targetType == "std::array<float, 3>");
|
||||
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<T>", 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<EnumDeclaration*>(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;
|
||||
}
|
||||
21
progress.md
21
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+
|
||||
|
||||
Reference in New Issue
Block a user