diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 6576aeb..2c9e4fa 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -202,6 +202,9 @@ target_include_directories(step69_test PRIVATE src) add_executable(step70_test tests/step70_test.cpp) target_include_directories(step70_test PRIVATE src) +add_executable(step71_test tests/step71_test.cpp) +target_include_directories(step71_test PRIVATE src) + add_executable(whetstone_editor src/main.cpp) target_include_directories(whetstone_editor PRIVATE src) # find_package(SDL2 REQUIRED) # Commented out for now to avoid build issues diff --git a/editor/tests/step71_test.cpp b/editor/tests/step71_test.cpp new file mode 100644 index 0000000..e1a25b6 --- /dev/null +++ b/editor/tests/step71_test.cpp @@ -0,0 +1,267 @@ +// Step 71 TDD Test: Incremental optimization with rollback +// +// Tests incremental transform tracking and selective undo: +// 1. Apply 3 transforms → all recorded in journal with provenance +// 2. Undo middle transform → AST reflects only transforms 1 and 3 +// 3. Undo all transforms → AST back to original state +// 4. Each node knows which transform created/modified it +// 5. Undo individual transform by ID +// +// Will fail until incremental optimization with rollback is implemented. + +#include +#include +#include +#include +#include "ast/ASTNode.h" +#include "ast/Module.h" +#include "ast/Function.h" +#include "ast/Variable.h" +#include "ast/Statement.h" +#include "ast/Expression.h" + +// Forward declaration — IncrementalOptimizer +class IncrementalOptimizer { +public: + struct TransformRecord { + std::string transformId; + std::string transformName; // e.g., "constant-fold", "dead-code-elim" + std::vector affectedNodeIds; + }; + + void setRoot(ASTNode* root); + + // Apply a named transform, returns its ID + std::string applyTransform(const std::string& transformName); + + // Get all applied transforms in order + std::vector getTransformHistory() const; + + // Undo a specific transform by ID + bool undoTransform(const std::string& transformId); + + // Undo the most recent transform + bool undoLast(); + + // Get which transform created/modified a given node + std::string getProvenance(const std::string& nodeId) const; +}; + +int main() { + int passed = 0; + int failed = 0; + + // --- Test 1: Apply transforms and record in journal --- + { + Module mod("m1", "Test", "python"); + Function* fn = new Function("f1", "calc"); + + // return (3 + 4) + (5 + 6) + Return* ret = new Return(); + ret->id = "r1"; + + BinaryOperation* outerAdd = new BinaryOperation("outer", "+"); + + BinaryOperation* leftAdd = new BinaryOperation("left", "+"); + IntegerLiteral* l1 = new IntegerLiteral("i1", 3); + IntegerLiteral* l2 = new IntegerLiteral("i2", 4); + leftAdd->setChild("left", l1); + leftAdd->setChild("right", l2); + + BinaryOperation* rightAdd = new BinaryOperation("right", "+"); + IntegerLiteral* l3 = new IntegerLiteral("i3", 5); + IntegerLiteral* l4 = new IntegerLiteral("i4", 6); + rightAdd->setChild("left", l3); + rightAdd->setChild("right", l4); + + outerAdd->setChild("left", leftAdd); + outerAdd->setChild("right", rightAdd); + ret->setChild("value", outerAdd); + fn->addChild("body", ret); + mod.addChild("functions", fn); + + IncrementalOptimizer opt; + opt.setRoot(&mod); + + // Apply constant folding multiple times + std::string t1 = opt.applyTransform("constant-fold"); + assert(!t1.empty() && "Should return a transform ID"); + + auto history = opt.getTransformHistory(); + assert(!history.empty() && "Should have at least one transform in history"); + + std::cout << "Test 1 PASS: Transform recorded in history" << std::endl; + ++passed; + + delete l4; + delete l3; + delete rightAdd; + delete l2; + delete l1; + delete leftAdd; + delete outerAdd; + delete ret; + delete fn; + } + + // --- Test 2: Undo last transform --- + { + Module mod("m1", "Test", "python"); + Function* fn = new Function("f1", "simple"); + + // return 3 + 4 + Return* ret = new Return(); + ret->id = "r1"; + BinaryOperation* binOp = new BinaryOperation("b1", "+"); + IntegerLiteral* left = new IntegerLiteral("i1", 3); + IntegerLiteral* right = new IntegerLiteral("i2", 4); + binOp->setChild("left", left); + binOp->setChild("right", right); + ret->setChild("value", binOp); + fn->addChild("body", ret); + mod.addChild("functions", fn); + + IncrementalOptimizer opt; + opt.setRoot(&mod); + + // Before transform + auto* valueBefore = ret->getChild("value"); + assert(valueBefore->conceptType == "BinaryOperation" && "Before: BinaryOperation"); + + // Apply constant fold + std::string tid = opt.applyTransform("constant-fold"); + + // After transform: should be IntegerLiteral(7) + auto* valueAfter = ret->getChild("value"); + assert(valueAfter->conceptType == "IntegerLiteral" && "After fold: IntegerLiteral"); + + // Undo + bool undone = opt.undoLast(); + assert(undone && "Undo should succeed"); + + // After undo: should be back to BinaryOperation + auto* valueUndone = ret->getChild("value"); + assert(valueUndone->conceptType == "BinaryOperation" && + "After undo: should be BinaryOperation again"); + + std::cout << "Test 2 PASS: Undo last transform restores original" << std::endl; + ++passed; + + delete right; + delete left; + delete binOp; + delete ret; + delete fn; + } + + // --- Test 3: Undo specific transform by ID --- + { + Module mod("m1", "Test", "python"); + Function* fn = new Function("f1", "multi"); + + // Build body: x = 1; return 3 + 4; y = 2; + Assignment* assign1 = new Assignment(); + assign1->id = "as1"; + VariableReference* t1 = new VariableReference("vr1", "x"); + IntegerLiteral* v1 = new IntegerLiteral("i1", 1); + assign1->setChild("target", t1); + assign1->setChild("value", v1); + fn->addChild("body", assign1); + + Return* ret = new Return(); + ret->id = "r1"; + BinaryOperation* binOp = new BinaryOperation("b1", "+"); + IntegerLiteral* left = new IntegerLiteral("i2", 3); + IntegerLiteral* right = new IntegerLiteral("i3", 4); + binOp->setChild("left", left); + binOp->setChild("right", right); + ret->setChild("value", binOp); + fn->addChild("body", ret); + + Assignment* assign2 = new Assignment(); + assign2->id = "as2"; + VariableReference* t2 = new VariableReference("vr2", "y"); + IntegerLiteral* v2 = new IntegerLiteral("i4", 2); + assign2->setChild("target", t2); + assign2->setChild("value", v2); + fn->addChild("body", assign2); + + mod.addChild("functions", fn); + + IncrementalOptimizer opt; + opt.setRoot(&mod); + + // Apply two transforms + std::string foldId = opt.applyTransform("constant-fold"); + std::string dceId = opt.applyTransform("dead-code-elim"); + + // Undo just the fold, keeping DCE + bool undone = opt.undoTransform(foldId); + assert(undone && "Should be able to undo specific transform"); + + // The constant fold should be undone (3+4 back to BinaryOperation) + // but dead code elimination should remain + auto history = opt.getTransformHistory(); + bool foldStillThere = false; + for (const auto& h : history) { + if (h.transformId == foldId) foldStillThere = true; + } + assert(!foldStillThere && "Undone transform should be removed from history"); + + std::cout << "Test 3 PASS: Undo specific transform by ID" << std::endl; + ++passed; + + delete v2; + delete t2; + delete assign2; + delete right; + delete left; + delete binOp; + delete ret; + delete v1; + delete t1; + delete assign1; + delete fn; + } + + // --- Test 4: Provenance tracking --- + { + Module mod("m1", "Test", "python"); + Function* fn = new Function("f1", "prov"); + + Return* ret = new Return(); + ret->id = "r1"; + BinaryOperation* binOp = new BinaryOperation("b1", "+"); + IntegerLiteral* left = new IntegerLiteral("i1", 10); + IntegerLiteral* right = new IntegerLiteral("i2", 20); + binOp->setChild("left", left); + binOp->setChild("right", right); + ret->setChild("value", binOp); + fn->addChild("body", ret); + mod.addChild("functions", fn); + + IncrementalOptimizer opt; + opt.setRoot(&mod); + + std::string tid = opt.applyTransform("constant-fold"); + + // The folded literal node should know which transform created it + auto* foldedNode = ret->getChild("value"); + std::string provenance = opt.getProvenance(foldedNode->id); + assert(!provenance.empty() && "Folded node should have provenance"); + assert(provenance == tid && "Provenance should match the transform ID"); + + std::cout << "Test 4 PASS: Provenance tracks which transform created node" << std::endl; + ++passed; + + delete right; + delete left; + delete binOp; + delete ret; + delete fn; + } + + // --- Summary --- + std::cout << "\n=== Step 71 Results: " << passed << " passed, " << failed << " failed ===" << std::endl; + return failed > 0 ? 1 : 0; +}