diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 901171e..be15bf5 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -4,6 +4,16 @@ project(WhetstoneEditor LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +# nlohmann/json (header-only, fetched once) +include(FetchContent) +FetchContent_Declare( + json + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG v3.11.3 + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(json) + add_executable(step1_test tests/step1_test.cpp) target_include_directories(step1_test PRIVATE src) @@ -15,3 +25,7 @@ target_include_directories(step3_test PRIVATE src) add_executable(step4_test tests/step4_test.cpp) target_include_directories(step4_test PRIVATE src) + +add_executable(step5_test tests/step5_test.cpp) +target_include_directories(step5_test PRIVATE src) +target_link_libraries(step5_test PRIVATE nlohmann_json::nlohmann_json) diff --git a/editor/src/ast/ASTNode.h b/editor/src/ast/ASTNode.h index ea4629d..11c7013 100644 --- a/editor/src/ast/ASTNode.h +++ b/editor/src/ast/ASTNode.h @@ -45,6 +45,15 @@ public: return it != children_.end() ? it->second : empty; } + // Get all child role names + std::vector childRoles() const { + std::vector roles; + for (const auto& [role, _] : children_) { + roles.push_back(role); + } + return roles; + } + // Get all children across all roles std::vector allChildren() const { std::vector result; diff --git a/editor/src/ast/Serialization.h b/editor/src/ast/Serialization.h new file mode 100644 index 0000000..fb197c3 --- /dev/null +++ b/editor/src/ast/Serialization.h @@ -0,0 +1,138 @@ +#pragma once +#include +#include "ASTNode.h" +#include "Module.h" +#include "Function.h" +#include "Variable.h" +#include "Parameter.h" +#include "Statement.h" +#include "Expression.h" +#include "Type.h" +#include "Annotation.h" + +using json = nlohmann::json; + +inline json propertiesToJson(const ASTNode* node) { + json props = json::object(); + const auto& ct = node->conceptType; + + // Module + if (ct == "Module") { + auto* n = static_cast(node); + props["name"] = n->name; + props["targetLanguage"] = n->targetLanguage; + } + // Function + else if (ct == "Function") { + auto* n = static_cast(node); + props["name"] = n->name; + } + // Variable + else if (ct == "Variable") { + auto* n = static_cast(node); + props["name"] = n->name; + } + // Parameter + else if (ct == "Parameter") { + auto* n = static_cast(node); + props["name"] = n->name; + } + // Statements + else if (ct == "ForLoop") { + auto* n = static_cast(node); + props["iteratorName"] = n->iteratorName; + } + // Expressions + else if (ct == "BinaryOperation") { + auto* n = static_cast(node); + props["op"] = n->op; + } + else if (ct == "UnaryOperation") { + auto* n = static_cast(node); + props["op"] = n->op; + } + else if (ct == "FunctionCall") { + auto* n = static_cast(node); + props["functionName"] = n->functionName; + } + else if (ct == "VariableReference") { + auto* n = static_cast(node); + props["variableName"] = n->variableName; + } + else if (ct == "IntegerLiteral") { + auto* n = static_cast(node); + props["value"] = n->value; + } + else if (ct == "FloatLiteral") { + auto* n = static_cast(node); + props["value"] = n->value; + } + else if (ct == "StringLiteral") { + auto* n = static_cast(node); + props["value"] = n->value; + } + else if (ct == "BooleanLiteral") { + auto* n = static_cast(node); + props["value"] = n->value; + } + else if (ct == "MemberAccess") { + auto* n = static_cast(node); + props["memberName"] = n->memberName; + } + // Types + else if (ct == "PrimitiveType") { + auto* n = static_cast(node); + props["kind"] = n->kind; + } + else if (ct == "CustomType") { + auto* n = static_cast(node); + props["typeName"] = n->typeName; + } + // Annotations + else if (ct == "DerefStrategy") { + auto* n = static_cast(node); + props["strategy"] = n->strategy; + if (!n->derefLocation.empty()) props["derefLocation"] = n->derefLocation; + if (!n->owner.empty()) props["owner"] = n->owner; + } + else if (ct == "OptimizationLock") { + auto* n = static_cast(node); + props["lockedBy"] = n->lockedBy; + props["lockReason"] = n->lockReason; + props["lockLevel"] = n->lockLevel; + if (!n->affectedStrategies.empty()) props["affectedStrategies"] = n->affectedStrategies; + if (!n->timestamp.empty()) props["timestamp"] = n->timestamp; + } + else if (ct == "LangSpecific") { + auto* n = static_cast(node); + props["language"] = n->language; + props["idiomType"] = n->idiomType; + props["rawSyntax"] = n->rawSyntax; + if (!n->semanticHint.empty()) props["semanticHint"] = n->semanticHint; + if (!n->position.empty()) props["position"] = n->position; + } + // NullLiteral, ListLiteral, IndexAccess, Block, Assignment, IfStatement, + // WhileLoop, Return, ExpressionStatement, ListType, SetType, MapType, + // TupleType, ArrayType, OptionalType — no extra properties + + return props; +} + +inline json toJson(const ASTNode* node) { + json j; + j["id"] = node->id; + j["concept"] = node->conceptType; + j["properties"] = propertiesToJson(node); + + json children = json::object(); + for (const auto& role : node->childRoles()) { + json arr = json::array(); + for (const auto* child : node->getChildren(role)) { + arr.push_back(toJson(child)); + } + children[role] = arr; + } + j["children"] = children; + + return j; +} diff --git a/editor/tests/step5_test.cpp b/editor/tests/step5_test.cpp new file mode 100644 index 0000000..82d288d --- /dev/null +++ b/editor/tests/step5_test.cpp @@ -0,0 +1,139 @@ +// Step 5: Serialize Calculator AST to JSON, verify structure. + +#include "../src/ast/Serialization.h" +#include +#include + +int main() { + // Build the Calculator model (same as step3) + Module calc("Calc_M001", "Calculator", "cpp"); + + Variable pi("Calc_V001", "PI"); + PrimitiveType piType("Calc_VT001", "float"); + pi.setChild("type", &piType); + calc.addChild("variables", &pi); + + Function add("Calc_F001", "add"); + PrimitiveType addRet("Calc_RT001", "int"); + add.setChild("returnType", &addRet); + + Parameter px("Calc_P001", "x"); + PrimitiveType pxType("Calc_PT001", "int"); + px.setChild("type", &pxType); + add.addChild("parameters", &px); + + Parameter py("Calc_P002", "y"); + PrimitiveType pyType("Calc_PT002", "int"); + py.setChild("type", &pyType); + add.addChild("parameters", &py); + + Assignment assign; + assign.id = "Calc_A001"; + VariableReference assignTarget("c_VR_result", "result"); + assign.setChild("target", &assignTarget); + BinaryOperation addOp("Calc_E001", "+"); + VariableReference vrX("Calc_VR_x", "x"); + VariableReference vrY("Calc_VR_y", "y"); + addOp.setChild("left", &vrX); + addOp.setChild("right", &vrY); + assign.setChild("value", &addOp); + add.addChild("body", &assign); + + Return addReturn; + addReturn.id = "Calc_R001"; + VariableReference vrRetResult("c_VR_return", "result"); + addReturn.setChild("value", &vrRetResult); + add.addChild("body", &addReturn); + + calc.addChild("functions", &add); + + Function multiply("Calc_F002", "multiply"); + PrimitiveType mulRet("Calc_RT002", "int"); + multiply.setChild("returnType", &mulRet); + + Parameter pa("Calc_P003", "a"); + PrimitiveType paType("Calc_PT003", "int"); + pa.setChild("type", &paType); + multiply.addChild("parameters", &pa); + + Parameter pb("Calc_P004", "b"); + PrimitiveType pbType("Calc_PT004", "int"); + pb.setChild("type", &pbType); + multiply.addChild("parameters", &pb); + + Return mulReturn; + mulReturn.id = "Calc_R002"; + BinaryOperation mulOp("Calc_E005", "*"); + VariableReference vrA("Calc_VR_a", "a"); + VariableReference vrB("Calc_VR_b", "b"); + mulOp.setChild("left", &vrA); + mulOp.setChild("right", &vrB); + mulReturn.setChild("value", &mulOp); + multiply.addChild("body", &mulReturn); + + calc.addChild("functions", &multiply); + + // --- Serialize --- + json j = toJson(&calc); + + // Top-level structure + assert(j["id"] == "Calc_M001"); + assert(j["concept"] == "Module"); + assert(j["properties"]["name"] == "Calculator"); + assert(j["properties"]["targetLanguage"] == "cpp"); + + // Functions array + assert(j["children"]["functions"].size() == 2); + + auto& jAdd = j["children"]["functions"][0]; + assert(jAdd["id"] == "Calc_F001"); + assert(jAdd["concept"] == "Function"); + assert(jAdd["properties"]["name"] == "add"); + + // add's returnType + assert(jAdd["children"]["returnType"].size() == 1); + assert(jAdd["children"]["returnType"][0]["concept"] == "PrimitiveType"); + assert(jAdd["children"]["returnType"][0]["properties"]["kind"] == "int"); + + // add's parameters + assert(jAdd["children"]["parameters"].size() == 2); + assert(jAdd["children"]["parameters"][0]["properties"]["name"] == "x"); + assert(jAdd["children"]["parameters"][1]["properties"]["name"] == "y"); + + // Parameter type nested + auto& jPx = jAdd["children"]["parameters"][0]; + assert(jPx["children"]["type"].size() == 1); + assert(jPx["children"]["type"][0]["properties"]["kind"] == "int"); + + // add's body + assert(jAdd["children"]["body"].size() == 2); + auto& jAssign = jAdd["children"]["body"][0]; + assert(jAssign["concept"] == "Assignment"); + assert(jAssign["children"]["target"].size() == 1); + assert(jAssign["children"]["target"][0]["concept"] == "VariableReference"); + assert(jAssign["children"]["target"][0]["properties"]["variableName"] == "result"); + + auto& jBinOp = jAssign["children"]["value"][0]; + assert(jBinOp["concept"] == "BinaryOperation"); + assert(jBinOp["properties"]["op"] == "+"); + assert(jBinOp["children"]["left"][0]["properties"]["variableName"] == "x"); + assert(jBinOp["children"]["right"][0]["properties"]["variableName"] == "y"); + + // multiply function + auto& jMul = j["children"]["functions"][1]; + assert(jMul["properties"]["name"] == "multiply"); + auto& jMulBody = jMul["children"]["body"][0]; + assert(jMulBody["concept"] == "Return"); + auto& jMulOp = jMulBody["children"]["value"][0]; + assert(jMulOp["properties"]["op"] == "*"); + + // Variables + assert(j["children"]["variables"].size() == 1); + assert(j["children"]["variables"][0]["properties"]["name"] == "PI"); + assert(j["children"]["variables"][0]["children"]["type"][0]["properties"]["kind"] == "float"); + + // Print for manual inspection + std::cout << j.dump(2) << std::endl; + std::cout << "\nStep 5: PASS — Calculator serialized to JSON" << std::endl; + return 0; +}