diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index a1e0bdd..a11b366 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -193,6 +193,9 @@ target_include_directories(step66_test PRIVATE src) add_executable(step67_test tests/step67_test.cpp) target_include_directories(step67_test PRIVATE src) +add_executable(step68_test tests/step68_test.cpp) +target_include_directories(step68_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/step68_test.cpp b/editor/tests/step68_test.cpp new file mode 100644 index 0000000..c023511 --- /dev/null +++ b/editor/tests/step68_test.cpp @@ -0,0 +1,216 @@ +// Step 68 TDD Test: Transformation engine +// +// Tests AST transformation framework: +// 1. Constant folding: 3 + 4 → 7 in AST +// 2. Dead code elimination: unreachable code after return removed +// 3. Transform checks OptimizationLock before modifying (warn, don't reject) +// 4. Respect @Loop annotations +// 5. Transform framework defines pre/post conditions +// +// Will fail until the transformation engine is implemented. + +#include +#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" +#include "ast/Annotation.h" + +// Forward declaration — TransformEngine +class TransformEngine { +public: + struct TransformResult { + bool applied; + std::string warning; // Non-empty if warning produced + int nodesModified; + }; + + // Set the AST root to transform + void setRoot(ASTNode* root); + + // Apply constant folding pass + TransformResult constantFolding(); + + // Apply dead code elimination pass + TransformResult deadCodeElimination(); + + // Apply all built-in transforms + std::vector applyAll(); +}; + +int main() { + int passed = 0; + int failed = 0; + + // --- Test 1: Constant folding 3 + 4 → 7 --- + { + Module mod("m1", "Test", "python"); + Function* fn = new Function("f1", "calc"); + + // 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); + + TransformEngine engine; + engine.setRoot(&mod); + + auto result = engine.constantFolding(); + assert(result.applied && "Constant folding should apply"); + assert(result.nodesModified > 0 && "Should modify at least one node"); + + // After folding, the return value should be IntegerLiteral(7) + auto* newValue = ret->getChild("value"); + assert(newValue != nullptr && "Return should still have a value"); + assert(newValue->conceptType == "IntegerLiteral" && + "Folded result should be IntegerLiteral"); + auto* foldedLit = static_cast(newValue); + assert(foldedLit->value == 7 && "3 + 4 should fold to 7"); + + std::cout << "Test 1 PASS: Constant folding 3 + 4 → 7" << std::endl; + ++passed; + + delete right; + delete left; + delete binOp; + delete ret; + delete fn; + } + + // --- Test 2: Dead code elimination removes code after return --- + { + Module mod("m1", "Test", "python"); + Function* fn = new Function("f1", "early"); + + // return 1; x = 2 (dead code) + Return* ret = new Return(); + ret->id = "r1"; + IntegerLiteral* retVal = new IntegerLiteral("i1", 1); + ret->setChild("value", retVal); + fn->addChild("body", ret); + + Assignment* deadAssign = new Assignment(); + deadAssign->id = "dead1"; + VariableReference* target = new VariableReference("vr1", "x"); + IntegerLiteral* val = new IntegerLiteral("i2", 2); + deadAssign->setChild("target", target); + deadAssign->setChild("value", val); + fn->addChild("body", deadAssign); + + mod.addChild("functions", fn); + + TransformEngine engine; + engine.setRoot(&mod); + + auto result = engine.deadCodeElimination(); + assert(result.applied && "DCE should apply"); + + // The assignment after return should be removed + auto body = fn->getChildren("body"); + assert(body.size() == 1 && "Only the return should remain"); + assert(body[0]->conceptType == "Return" && "Remaining stmt should be Return"); + + std::cout << "Test 2 PASS: Dead code after return eliminated" << std::endl; + ++passed; + + delete val; + delete target; + delete deadAssign; + delete retVal; + delete ret; + delete fn; + } + + // --- Test 3: Locked node produces warning but still transforms --- + { + Module mod("m1", "Test", "python"); + Function* fn = new Function("f1", "locked"); + + OptimizationLock* lock = new OptimizationLock(); + lock->id = "lock1"; + lock->lockedBy = "user"; + lock->lockReason = "Don't optimize"; + lock->lockLevel = "warning"; + fn->addChild("annotations", lock); + + // return 5 + 3 + Return* ret = new Return(); + ret->id = "r1"; + BinaryOperation* binOp = new BinaryOperation("b1", "+"); + IntegerLiteral* left = new IntegerLiteral("i1", 5); + IntegerLiteral* right = new IntegerLiteral("i2", 3); + binOp->setChild("left", left); + binOp->setChild("right", right); + ret->setChild("value", binOp); + fn->addChild("body", ret); + mod.addChild("functions", fn); + + TransformEngine engine; + engine.setRoot(&mod); + + auto result = engine.constantFolding(); + assert(result.applied && "Should still apply (warning level, not block)"); + assert(!result.warning.empty() && "Should produce a warning for locked node"); + + // Should still fold + auto* newValue = ret->getChild("value"); + assert(newValue->conceptType == "IntegerLiteral" && "Should still fold"); + auto* lit = static_cast(newValue); + assert(lit->value == 8 && "5 + 3 should fold to 8"); + + std::cout << "Test 3 PASS: Locked node warns but still transforms" << std::endl; + ++passed; + + delete right; + delete left; + delete binOp; + delete ret; + delete lock; + delete fn; + } + + // --- Test 4: No transformation needed → result.applied is false --- + { + Module mod("m1", "Test", "python"); + Function* fn = new Function("f1", "simple"); + + // return x (nothing to fold) + Return* ret = new Return(); + ret->id = "r1"; + VariableReference* ref = new VariableReference("vr1", "x"); + ret->setChild("value", ref); + fn->addChild("body", ret); + mod.addChild("functions", fn); + + TransformEngine engine; + engine.setRoot(&mod); + + auto result = engine.constantFolding(); + assert(!result.applied && "No constants to fold"); + assert(result.nodesModified == 0 && "No nodes should be modified"); + + std::cout << "Test 4 PASS: No-op when nothing to transform" << std::endl; + ++passed; + + delete ref; + delete ret; + delete fn; + } + + // --- Summary --- + std::cout << "\n=== Step 68 Results: " << passed << " passed, " << failed << " failed ===" << std::endl; + return failed > 0 ? 1 : 0; +}