From 96799dd51f29ff8c6fbe4bdc1646dea0abed57a3 Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 16 Feb 2026 00:36:25 -0700 Subject: [PATCH] Step 337: Preprocessor AST Nodes (12/12 tests) Added IncludeDirective, PragmaDirective, MacroDefinition as first-class AST nodes. Full serialization roundtrip and CompactAST support. Preprocessor directives are statement-level nodes with macro bodies kept as unparsed text. Co-Authored-By: Claude Opus 4.6 --- editor/CMakeLists.txt | 4 + editor/src/CompactAST.h | 7 + editor/src/ast/PreprocessorNodes.h | 47 +++++++ editor/src/ast/Serialization.h | 42 ++++++ editor/tests/step337_test.cpp | 210 +++++++++++++++++++++++++++++ progress.md | 23 ++++ 6 files changed, 333 insertions(+) create mode 100644 editor/src/ast/PreprocessorNodes.h create mode 100644 editor/tests/step337_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 3d1b6cc..1386fe4 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -2027,4 +2027,8 @@ target_link_libraries(step336_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +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) + # 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 5290534..4126bee 100644 --- a/editor/src/CompactAST.h +++ b/editor/src/CompactAST.h @@ -424,6 +424,13 @@ inline std::string getNodeName(const ASTNode* node) { return static_cast(node)->moduleName; if (ct == "ScheduleTask") return static_cast(node)->queue; + // Preprocessor nodes (Step 337) + if (ct == "IncludeDirective") + return static_cast(node)->path; + if (ct == "PragmaDirective") + return static_cast(node)->directive; + if (ct == "MacroDefinition") + return static_cast(node)->name; return ""; } diff --git a/editor/src/ast/PreprocessorNodes.h b/editor/src/ast/PreprocessorNodes.h new file mode 100644 index 0000000..9a0856c --- /dev/null +++ b/editor/src/ast/PreprocessorNodes.h @@ -0,0 +1,47 @@ +#pragma once +#include "ASTNode.h" +#include +#include + +// Preprocessor directive AST nodes — statement-level, added to Module children. +// Preprocessor operates on text, not AST; macro bodies stay as unparsed text. + +class IncludeDirective : public ASTNode { +public: + std::string path; // "ast/ASTNode.h" or "string" + bool isSystem = false; // angle brackets (true) vs quotes (false) + + IncludeDirective() { conceptType = "IncludeDirective"; } + IncludeDirective(const std::string& id, const std::string& p, bool sys = false) + : path(p), isSystem(sys) { + this->id = id; + conceptType = "IncludeDirective"; + } +}; + +class PragmaDirective : public ASTNode { +public: + std::string directive; // "once", "pack(push, 1)", etc. + + PragmaDirective() { conceptType = "PragmaDirective"; } + PragmaDirective(const std::string& id, const std::string& dir) + : directive(dir) { + this->id = id; + conceptType = "PragmaDirective"; + } +}; + +class MacroDefinition : public ASTNode { +public: + std::string name; // Macro name + std::vector parameters; // Empty for object-like macros + std::string body; // Unparsed macro body text + bool isFunctionLike = false; // Has parameter list + + MacroDefinition() { conceptType = "MacroDefinition"; } + MacroDefinition(const std::string& id, const std::string& n) + : name(n) { + this->id = id; + conceptType = "MacroDefinition"; + } +}; diff --git a/editor/src/ast/Serialization.h b/editor/src/ast/Serialization.h index 89bc860..ff0ef5e 100644 --- a/editor/src/ast/Serialization.h +++ b/editor/src/ast/Serialization.h @@ -17,6 +17,7 @@ #include "ClassDeclaration.h" #include "GenericType.h" #include "AsyncNodes.h" +#include "PreprocessorNodes.h" using json = nlohmann::json; @@ -505,6 +506,23 @@ inline json propertiesToJson(const ASTNode* node) { auto* n = static_cast(node); props["name"] = n->name; } + // Preprocessor nodes (Step 337) + else if (ct == "IncludeDirective") { + auto* n = static_cast(node); + props["path"] = n->path; + if (n->isSystem) props["isSystem"] = n->isSystem; + } + else if (ct == "PragmaDirective") { + auto* n = static_cast(node); + props["directive"] = n->directive; + } + else if (ct == "MacroDefinition") { + auto* n = static_cast(node); + props["name"] = n->name; + if (!n->body.empty()) props["body"] = n->body; + if (n->isFunctionLike) props["isFunctionLike"] = n->isFunctionLike; + if (!n->parameters.empty()) props["parameters"] = n->parameters; + } // NullLiteral, ListLiteral, IndexAccess, Block, Assignment, IfStatement, // WhileLoop, Return, ExpressionStatement, ListType, SetType, MapType, // TupleType, ArrayType, OptionalType — no extra properties @@ -668,6 +686,9 @@ inline ASTNode* createNode(const std::string& conceptName) { if (conceptName == "AwaitExpression") return new AwaitExpression(); if (conceptName == "LambdaExpression") return new LambdaExpression(); if (conceptName == "DecoratorAnnotation") return new DecoratorAnnotation(); + if (conceptName == "IncludeDirective") return new IncludeDirective(); + if (conceptName == "PragmaDirective") return new PragmaDirective(); + if (conceptName == "MacroDefinition") return new MacroDefinition(); return nullptr; } @@ -1188,6 +1209,27 @@ inline void setPropertiesFromJson(ASTNode* node, const json& props) { auto* n = static_cast(node); if (props.contains("name")) n->name = props["name"].get(); } + // Preprocessor nodes (Step 337) + else if (ct == "IncludeDirective") { + auto* n = static_cast(node); + if (props.contains("path")) n->path = props["path"].get(); + if (props.contains("isSystem")) n->isSystem = props["isSystem"].get(); + } + else if (ct == "PragmaDirective") { + auto* n = static_cast(node); + if (props.contains("directive")) n->directive = props["directive"].get(); + } + else if (ct == "MacroDefinition") { + auto* n = static_cast(node); + if (props.contains("name")) n->name = props["name"].get(); + if (props.contains("body")) n->body = props["body"].get(); + if (props.contains("isFunctionLike")) n->isFunctionLike = props["isFunctionLike"].get(); + if (props.contains("parameters") && props["parameters"].is_array()) { + n->parameters.clear(); + for (const auto& p : props["parameters"]) + if (p.is_string()) n->parameters.push_back(p.get()); + } + } } inline std::string generateNodeId() { diff --git a/editor/tests/step337_test.cpp b/editor/tests/step337_test.cpp new file mode 100644 index 0000000..e990119 --- /dev/null +++ b/editor/tests/step337_test.cpp @@ -0,0 +1,210 @@ +// Step 337: Preprocessor AST Nodes (12 tests) +// Tests IncludeDirective, PragmaDirective, MacroDefinition construction, +// serialization roundtrip, and CompactAST output. + +#include +#include +#include +#include "ast/PreprocessorNodes.h" +#include "ast/Serialization.h" +#include "CompactAST.h" +#include "ast/Module.h" +#include "ast/Function.h" + +int main() { + int passed = 0; + + // Test 1: IncludeDirective construction — local include + { + auto* inc = new IncludeDirective("inc1", "ast/ASTNode.h", false); + assert(inc->conceptType == "IncludeDirective"); + assert(inc->path == "ast/ASTNode.h"); + assert(!inc->isSystem); + delete inc; + std::cout << "Test 1 PASSED: IncludeDirective local include\n"; + passed++; + } + + // Test 2: IncludeDirective — system include + { + auto* inc = new IncludeDirective("inc2", "string", true); + assert(inc->isSystem); + assert(inc->path == "string"); + delete inc; + std::cout << "Test 2 PASSED: IncludeDirective system include\n"; + passed++; + } + + // Test 3: IncludeDirective JSON roundtrip + { + auto* inc = new IncludeDirective("inc3", "vector", true); + json j = toJson(inc); + auto* restored = fromJson(j); + assert(restored->conceptType == "IncludeDirective"); + auto* ri = static_cast(restored); + assert(ri->path == "vector"); + assert(ri->isSystem == true); + delete inc; + delete restored; + std::cout << "Test 3 PASSED: IncludeDirective JSON roundtrip\n"; + passed++; + } + + // Test 4: PragmaDirective construction + { + auto* prag = new PragmaDirective("prag1", "once"); + assert(prag->conceptType == "PragmaDirective"); + assert(prag->directive == "once"); + delete prag; + std::cout << "Test 4 PASSED: PragmaDirective construction\n"; + passed++; + } + + // Test 5: PragmaDirective JSON roundtrip + { + auto* prag = new PragmaDirective("prag2", "pack(push, 1)"); + json j = toJson(prag); + auto* restored = fromJson(j); + assert(restored->conceptType == "PragmaDirective"); + auto* rp = static_cast(restored); + assert(rp->directive == "pack(push, 1)"); + delete prag; + delete restored; + std::cout << "Test 5 PASSED: PragmaDirective JSON roundtrip\n"; + passed++; + } + + // Test 6: MacroDefinition — object-like macro + { + auto* mac = new MacroDefinition("mac1", "MAX_SIZE"); + mac->body = "1024"; + mac->isFunctionLike = false; + assert(mac->conceptType == "MacroDefinition"); + assert(mac->name == "MAX_SIZE"); + assert(mac->body == "1024"); + assert(!mac->isFunctionLike); + assert(mac->parameters.empty()); + delete mac; + std::cout << "Test 6 PASSED: MacroDefinition object-like macro\n"; + passed++; + } + + // Test 7: MacroDefinition — function-like macro with params + { + auto* mac = new MacroDefinition("mac2", "MIN"); + mac->isFunctionLike = true; + mac->parameters = {"a", "b"}; + mac->body = "((a) < (b) ? (a) : (b))"; + assert(mac->isFunctionLike); + assert(mac->parameters.size() == 2); + assert(mac->parameters[0] == "a"); + assert(mac->parameters[1] == "b"); + delete mac; + std::cout << "Test 7 PASSED: MacroDefinition function-like macro\n"; + passed++; + } + + // Test 8: MacroDefinition JSON roundtrip + { + auto* mac = new MacroDefinition("mac3", "CONCAT"); + mac->isFunctionLike = true; + mac->parameters = {"x", "y"}; + mac->body = "x##y"; + json j = toJson(mac); + auto* restored = fromJson(j); + assert(restored->conceptType == "MacroDefinition"); + auto* rm = static_cast(restored); + assert(rm->name == "CONCAT"); + assert(rm->isFunctionLike); + assert(rm->parameters.size() == 2); + assert(rm->parameters[0] == "x"); + assert(rm->body == "x##y"); + delete mac; + delete restored; + std::cout << "Test 8 PASSED: MacroDefinition JSON roundtrip\n"; + passed++; + } + + // Test 9: CompactAST names + { + auto* inc = new IncludeDirective("inc4", "iostream", true); + assert(getNodeName(inc) == "iostream"); + auto* prag = new PragmaDirective("prag3", "once"); + assert(getNodeName(prag) == "once"); + auto* mac = new MacroDefinition("mac4", "DEBUG_LOG"); + assert(getNodeName(mac) == "DEBUG_LOG"); + delete inc; delete prag; delete mac; + std::cout << "Test 9 PASSED: CompactAST node names\n"; + passed++; + } + + // Test 10: Module with mixed preprocessor + function nodes + { + auto* mod = new Module(); + mod->id = "mod1"; + mod->name = "test_module"; + auto* inc = new IncludeDirective("inc5", "stdio.h", true); + auto* prag = new PragmaDirective("prag4", "once"); + auto* mac = new MacroDefinition("mac5", "VERSION"); + mac->body = "\"1.0\""; + auto* func = new Function("fn1", "main"); + mod->addChild("statements", inc); + mod->addChild("statements", prag); + mod->addChild("statements", mac); + mod->addChild("functions", func); + + auto stmts = mod->getChildren("statements"); + assert(stmts.size() == 3); + assert(stmts[0]->conceptType == "IncludeDirective"); + assert(stmts[1]->conceptType == "PragmaDirective"); + assert(stmts[2]->conceptType == "MacroDefinition"); + delete mod; + std::cout << "Test 10 PASSED: Module with mixed preprocessor + function\n"; + passed++; + } + + // Test 11: Empty macro body + { + auto* mac = new MacroDefinition("mac6", "NDEBUG"); + assert(mac->body.empty()); + json j = toJson(mac); + auto* restored = fromJson(j); + auto* rm = static_cast(restored); + assert(rm->name == "NDEBUG"); + assert(rm->body.empty()); + assert(!rm->isFunctionLike); + delete mac; + delete restored; + std::cout << "Test 11 PASSED: Empty macro body\n"; + passed++; + } + + // Test 12: Serialization createNode dispatch for all 3 types + { + json jInc = {{"concept", "IncludeDirective"}, {"id", "test_inc"}, + {"properties", {{"path", "test.h"}, {"isSystem", false}}}}; + auto* rInc = fromJson(jInc); + assert(rInc != nullptr); + assert(rInc->conceptType == "IncludeDirective"); + assert(static_cast(rInc)->path == "test.h"); + + json jPrag = {{"concept", "PragmaDirective"}, {"id", "test_prag"}, + {"properties", {{"directive", "warning disable"}}}}; + auto* rPrag = fromJson(jPrag); + assert(rPrag != nullptr); + assert(static_cast(rPrag)->directive == "warning disable"); + + json jMac = {{"concept", "MacroDefinition"}, {"id", "test_mac"}, + {"properties", {{"name", "FOO"}, {"body", "bar"}}}}; + auto* rMac = fromJson(jMac); + assert(rMac != nullptr); + assert(static_cast(rMac)->name == "FOO"); + + delete rInc; delete rPrag; delete rMac; + std::cout << "Test 12 PASSED: Serialization createNode dispatch\n"; + passed++; + } + + std::cout << "\nResults: " << passed << "/12 tests passed\n"; + return (passed == 12) ? 0 : 1; +} diff --git a/progress.md b/progress.md index bb5e38c..02a488e 100644 --- a/progress.md +++ b/progress.md @@ -1561,6 +1561,29 @@ verify diamond detection, and JSON serialization roundtrip. parse→generate roundtrip, C++→Java extends+implements, C++→Python multi-base, template 3 params, diamond inheritance detection, mixed template+inheritance+virtual pipeline +## Phase 12d: C++ AST Depth — Preprocessor, Enums, Namespaces + +### Step 337: Preprocessor AST Nodes +**Status:** PASS (12/12 tests) + +Added first-class AST nodes for C++ preprocessor directives: IncludeDirective (path, +isSystem), PragmaDirective (directive), MacroDefinition (name, parameters, body, +isFunctionLike). Statement-level nodes that attach to Module children. Full JSON +serialization roundtrip and CompactAST node name support. + +**Files created:** +- `editor/src/ast/PreprocessorNodes.h` — IncludeDirective, PragmaDirective, MacroDefinition +- `editor/tests/step337_test.cpp` — 12 tests: construction, system vs local include, + JSON roundtrip, pragma, function-like vs object-like macros, CompactAST names, module + with mixed nodes, empty body, createNode dispatch + +**Files modified:** +- `editor/src/ast/Serialization.h` — propertiesToJson/createNode/setPropertiesFromJson + for all 3 preprocessor types +- `editor/src/CompactAST.h` — getNodeName for IncludeDirective, PragmaDirective, + MacroDefinition +- `editor/CMakeLists.txt` — step337_test target + --- # Roadmap Planning — Sprints 12-25+