diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index be15bf5..4ab4120 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -29,3 +29,7 @@ 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) + +add_executable(step6_test tests/step6_test.cpp) +target_include_directories(step6_test PRIVATE src) +target_link_libraries(step6_test PRIVATE nlohmann_json::nlohmann_json) diff --git a/editor/src/ast/Serialization.h b/editor/src/ast/Serialization.h index fb197c3..d213270 100644 --- a/editor/src/ast/Serialization.h +++ b/editor/src/ast/Serialization.h @@ -136,3 +136,165 @@ inline json toJson(const ASTNode* node) { return j; } + +// --- Deserialization --- + +inline ASTNode* createNode(const std::string& conceptName) { + if (conceptName == "Module") return new Module(); + if (conceptName == "Function") return new Function(); + if (conceptName == "Variable") return new Variable(); + if (conceptName == "Parameter") return new Parameter(); + if (conceptName == "Block") return new Block(); + if (conceptName == "Assignment") return new Assignment(); + if (conceptName == "IfStatement") return new IfStatement(); + if (conceptName == "WhileLoop") return new WhileLoop(); + if (conceptName == "ForLoop") return new ForLoop(); + if (conceptName == "Return") return new Return(); + if (conceptName == "ExpressionStatement") return new ExpressionStatement(); + if (conceptName == "BinaryOperation") return new BinaryOperation(); + if (conceptName == "UnaryOperation") return new UnaryOperation(); + if (conceptName == "FunctionCall") return new FunctionCall(); + if (conceptName == "VariableReference") return new VariableReference(); + if (conceptName == "IntegerLiteral") return new IntegerLiteral(); + if (conceptName == "FloatLiteral") return new FloatLiteral(); + if (conceptName == "StringLiteral") return new StringLiteral(); + if (conceptName == "BooleanLiteral") return new BooleanLiteral(); + if (conceptName == "NullLiteral") return new NullLiteral(); + if (conceptName == "ListLiteral") return new ListLiteral(); + if (conceptName == "IndexAccess") return new IndexAccess(); + if (conceptName == "MemberAccess") return new MemberAccess(); + if (conceptName == "PrimitiveType") return new PrimitiveType(); + if (conceptName == "ListType") return new ListType(); + if (conceptName == "SetType") return new SetType(); + if (conceptName == "MapType") return new MapType(); + if (conceptName == "TupleType") return new TupleType(); + if (conceptName == "ArrayType") return new ArrayType(); + if (conceptName == "OptionalType") return new OptionalType(); + if (conceptName == "CustomType") return new CustomType(); + if (conceptName == "DerefStrategy") return new DerefStrategy(); + if (conceptName == "OptimizationLock") return new OptimizationLock(); + if (conceptName == "LangSpecific") return new LangSpecific(); + return nullptr; +} + +inline void setPropertiesFromJson(ASTNode* node, const json& props) { + const auto& ct = node->conceptType; + + if (ct == "Module") { + auto* n = static_cast(node); + if (props.contains("name")) n->name = props["name"].get(); + if (props.contains("targetLanguage")) n->targetLanguage = props["targetLanguage"].get(); + } + else if (ct == "Function") { + auto* n = static_cast(node); + if (props.contains("name")) n->name = props["name"].get(); + } + else if (ct == "Variable") { + auto* n = static_cast(node); + if (props.contains("name")) n->name = props["name"].get(); + } + else if (ct == "Parameter") { + auto* n = static_cast(node); + if (props.contains("name")) n->name = props["name"].get(); + } + else if (ct == "ForLoop") { + auto* n = static_cast(node); + if (props.contains("iteratorName")) n->iteratorName = props["iteratorName"].get(); + } + else if (ct == "BinaryOperation") { + auto* n = static_cast(node); + if (props.contains("op")) n->op = props["op"].get(); + } + else if (ct == "UnaryOperation") { + auto* n = static_cast(node); + if (props.contains("op")) n->op = props["op"].get(); + } + else if (ct == "FunctionCall") { + auto* n = static_cast(node); + if (props.contains("functionName")) n->functionName = props["functionName"].get(); + } + else if (ct == "VariableReference") { + auto* n = static_cast(node); + if (props.contains("variableName")) n->variableName = props["variableName"].get(); + } + else if (ct == "IntegerLiteral") { + auto* n = static_cast(node); + if (props.contains("value")) n->value = props["value"].get(); + } + else if (ct == "FloatLiteral") { + auto* n = static_cast(node); + if (props.contains("value")) n->value = props["value"].get(); + } + else if (ct == "StringLiteral") { + auto* n = static_cast(node); + if (props.contains("value")) n->value = props["value"].get(); + } + else if (ct == "BooleanLiteral") { + auto* n = static_cast(node); + if (props.contains("value")) n->value = props["value"].get(); + } + else if (ct == "MemberAccess") { + auto* n = static_cast(node); + if (props.contains("memberName")) n->memberName = props["memberName"].get(); + } + else if (ct == "PrimitiveType") { + auto* n = static_cast(node); + if (props.contains("kind")) n->kind = props["kind"].get(); + } + else if (ct == "CustomType") { + auto* n = static_cast(node); + if (props.contains("typeName")) n->typeName = props["typeName"].get(); + } + else if (ct == "DerefStrategy") { + auto* n = static_cast(node); + if (props.contains("strategy")) n->strategy = props["strategy"].get(); + if (props.contains("derefLocation")) n->derefLocation = props["derefLocation"].get(); + if (props.contains("owner")) n->owner = props["owner"].get(); + } + else if (ct == "OptimizationLock") { + auto* n = static_cast(node); + if (props.contains("lockedBy")) n->lockedBy = props["lockedBy"].get(); + if (props.contains("lockReason")) n->lockReason = props["lockReason"].get(); + if (props.contains("lockLevel")) n->lockLevel = props["lockLevel"].get(); + if (props.contains("affectedStrategies")) n->affectedStrategies = props["affectedStrategies"].get(); + if (props.contains("timestamp")) n->timestamp = props["timestamp"].get(); + } + else if (ct == "LangSpecific") { + auto* n = static_cast(node); + if (props.contains("language")) n->language = props["language"].get(); + if (props.contains("idiomType")) n->idiomType = props["idiomType"].get(); + if (props.contains("rawSyntax")) n->rawSyntax = props["rawSyntax"].get(); + if (props.contains("semanticHint")) n->semanticHint = props["semanticHint"].get(); + if (props.contains("position")) n->position = props["position"].get(); + } +} + +inline ASTNode* fromJson(const json& j) { + ASTNode* node = createNode(j["concept"].get()); + if (!node) return nullptr; + + node->id = j["id"].get(); + + if (j.contains("properties")) { + setPropertiesFromJson(node, j["properties"]); + } + + if (j.contains("children")) { + for (auto& [role, arr] : j["children"].items()) { + for (auto& childJson : arr) { + ASTNode* child = fromJson(childJson); + if (child) node->addChild(role, child); + } + } + } + + return node; +} + +inline void deleteTree(ASTNode* node) { + if (!node) return; + for (auto* child : node->allChildren()) { + deleteTree(child); + } + delete node; +} diff --git a/editor/tests/step6_test.cpp b/editor/tests/step6_test.cpp new file mode 100644 index 0000000..9e3853d --- /dev/null +++ b/editor/tests/step6_test.cpp @@ -0,0 +1,127 @@ +// Step 6: JSON round-trip. Save Calculator → load → save again → compare (byte-identical). + +#include "../src/ast/Serialization.h" +#include +#include + +int main() { + // Build Calculator (same as step5) + 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); + + // --- Round-trip --- + + // Save 1 + json j1 = toJson(&calc); + std::string s1 = j1.dump(2); + + // Load + ASTNode* loaded = fromJson(j1); + assert(loaded != nullptr); + + // Verify loaded tree structure + assert(loaded->conceptType == "Module"); + auto* lm = static_cast(loaded); + assert(lm->name == "Calculator"); + assert(lm->targetLanguage == "cpp"); + assert(lm->getChildren("functions").size() == 2); + assert(lm->getChildren("variables").size() == 1); + + // Verify deep node + auto* lf = static_cast(lm->getChildren("functions")[0]); + assert(lf->name == "add"); + assert(lf->parent == loaded); + assert(lf->getChildren("parameters").size() == 2); + assert(lf->getChildren("body").size() == 2); + + auto* lp = static_cast(lf->getChildren("parameters")[0]); + assert(lp->name == "x"); + assert(lp->parent == lf); + auto* lpt = static_cast(lp->getChild("type")); + assert(lpt->kind == "int"); + assert(lpt->parent == lp); + + auto* la = static_cast(lf->getChildren("body")[0]); + auto* lbo = static_cast(la->getChild("value")); + assert(lbo->op == "+"); + assert(static_cast(lbo->getChild("left"))->variableName == "x"); + + // Save 2 + json j2 = toJson(loaded); + std::string s2 = j2.dump(2); + + // Compare: must be byte-identical + if (s1 != s2) { + std::cerr << "MISMATCH!\n--- Save 1 ---\n" << s1 + << "\n--- Save 2 ---\n" << s2 << std::endl; + assert(false && "Round-trip JSON not identical"); + } + + // Cleanup heap-allocated tree + deleteTree(loaded); + + std::cout << "Step 6: PASS — JSON round-trip (save/load/save identical)" << std::endl; + return 0; +}