diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 84651b3..69f31d6 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -2533,4 +2533,13 @@ target_link_libraries(step405_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step406_test tests/step406_test.cpp) +target_include_directories(step406_test PRIVATE src) +target_link_libraries(step406_test PRIVATE + nlohmann_json::nlohmann_json + unofficial::tree-sitter::tree-sitter + tree_sitter_python tree_sitter_cpp tree_sitter_elisp + tree_sitter_javascript tree_sitter_typescript + tree_sitter_java tree_sitter_rust tree_sitter_go) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/Pipeline.h b/editor/src/Pipeline.h index 3c5ae9a..13a8138 100644 --- a/editor/src/Pipeline.h +++ b/editor/src/Pipeline.h @@ -176,6 +176,9 @@ public: } else if (language == "csharp") { CSharpGenerator gen; return gen.generate(ast); + } else if (language == "fsharp" || language == "f#" || language == "fs") { + FSharpGenerator gen; + return gen.generate(ast); } else if (language == "c") { CGenerator gen; return gen.generate(ast); diff --git a/editor/src/ast/FSharpGenerator.h b/editor/src/ast/FSharpGenerator.h new file mode 100644 index 0000000..256abaf --- /dev/null +++ b/editor/src/ast/FSharpGenerator.h @@ -0,0 +1,348 @@ +#pragma once + +#include "ProjectionGenerator.h" +#include "ClassDeclaration.h" +#include "AsyncNodes.h" +#include "EnumNamespaceNodes.h" +#include "../SemannoAnnotationImpl.h" + +class FSharpGenerator : public ProjectionGenerator, public SemannoAnnotationImpl { +public: + std::string commentPrefix() const { return "// "; } + + std::string generate(const ASTNode* node) override { + return dispatchGenerate(this, node, "// Unknown concept: "); + } + + std::string visitModule(const Module* module) override { + std::ostringstream oss; + oss << "// Module: " << module->name << "\n\n"; + + for (const auto* stmt : module->getChildren("statements")) { + oss << generate(stmt) << "\n"; + } + if (!module->getChildren("statements").empty()) oss << "\n"; + + for (const auto* var : module->getChildren("variables")) { + oss << generate(var) << "\n"; + } + if (!module->getChildren("variables").empty()) oss << "\n"; + + for (const auto* fn : module->getChildren("functions")) { + oss << generate(fn) << "\n"; + } + + return oss.str(); + } + + std::string visitFunction(const Function* function) override { + std::ostringstream oss; + for (const auto* anno : function->getChildren("annotations")) { + oss << generate(anno) << "\n"; + } + + oss << "let " << function->name; + auto params = function->getChildren("parameters"); + for (const auto* p : params) { + oss << " " << static_cast(p)->name; + } + oss << " ="; + + auto body = function->getChildren("body"); + if (body.empty()) { + oss << " ()"; + return oss.str(); + } + + if (body.size() == 1) { + oss << " " << generate(body[0]); + return oss.str(); + } + + oss << "\n"; + for (const auto* stmt : body) { + oss << " " << generate(stmt) << "\n"; + } + return trimTrailingNewline(oss.str()); + } + + std::string visitAsyncFunction(const ASTNode* node) override { + auto* fn = static_cast(node); + std::ostringstream oss; + for (const auto* anno : fn->getChildren("annotations")) { + oss << generate(anno) << "\n"; + } + oss << "let " << fn->name; + auto params = fn->getChildren("parameters"); + for (const auto* p : params) { + oss << " " << static_cast(p)->name; + } + oss << " = async {\n"; + auto body = fn->getChildren("body"); + for (const auto* stmt : body) { + oss << " " << generate(stmt) << "\n"; + } + oss << "}"; + return oss.str(); + } + + std::string visitVariable(const Variable* variable) override { + bool isMutable = false; + for (const auto* anno : variable->getChildren("annotations")) { + if (anno->conceptType == "MutAnnotation") { + isMutable = true; + break; + } + } + + std::ostringstream oss; + oss << "let "; + if (isMutable) oss << "mutable "; + oss << variable->name; + auto init = variable->getChild("initializer"); + if (init) oss << " = " << generate(init); + return oss.str(); + } + + std::string visitParameter(const Parameter* parameter) override { + return parameter->name; + } + + std::string visitReturn(const Return* r) override { + auto value = r->getChild("value"); + return value ? generate(value) : "()"; + } + + std::string visitAssignment(const Assignment* a) override { + auto target = a->getChild("target"); + auto value = a->getChild("value"); + return (target ? generate(target) : "_") + " <- " + (value ? generate(value) : "()"); + } + + std::string visitIfStatement(const IfStatement* s) override { + std::ostringstream oss; + auto cond = s->getChild("condition"); + oss << "if " << (cond ? generate(cond) : "true") << " then"; + auto thenBranch = s->getChildren("thenBranch"); + auto elseBranch = s->getChildren("elseBranch"); + if (!thenBranch.empty()) oss << " " << generate(thenBranch.front()); + else oss << " ()"; + if (!elseBranch.empty()) oss << " else " << generate(elseBranch.front()); + return oss.str(); + } + + std::string visitExpressionStatement(const ExpressionStatement* s) override { + auto expr = s->getChild("expression"); + return expr ? generate(expr) : "()"; + } + + std::string visitBinaryOperation(const BinaryOperation* b) override { + auto left = b->getChild("left"); + auto right = b->getChild("right"); + return (left ? generate(left) : "") + " " + b->op + " " + (right ? generate(right) : ""); + } + + std::string visitUnaryOperation(const UnaryOperation* u) override { + auto operand = u->getChild("operand"); + return u->op + (operand ? generate(operand) : ""); + } + + std::string visitFunctionCall(const FunctionCall* c) override { + // Parser uses this marker for F# pipe chains. + if (c->functionName == "pipeline") return "|> pipeline"; + + std::ostringstream oss; + oss << c->functionName; + auto args = c->getChildren("arguments"); + for (const auto* arg : args) oss << " " << generate(arg); + return oss.str(); + } + + std::string visitWhileLoop(const WhileLoop* l) override { + std::ostringstream oss; + auto cond = l->getChild("condition"); + oss << "while " << (cond ? generate(cond) : "true") << " do"; + auto body = l->getChildren("body"); + if (body.empty()) return oss.str() + " ()"; + oss << "\n"; + for (const auto* s : body) oss << " " << generate(s) << "\n"; + return trimTrailingNewline(oss.str()); + } + + std::string visitForLoop(const ForLoop* l) override { + std::ostringstream oss; + auto iter = l->getChild("iterable"); + oss << "for " << (l->iteratorName.empty() ? "item" : l->iteratorName) + << " in " << (iter ? generate(iter) : "[]") << " do"; + auto body = l->getChildren("body"); + if (body.empty()) return oss.str() + " ()"; + oss << "\n"; + for (const auto* s : body) oss << " " << generate(s) << "\n"; + return trimTrailingNewline(oss.str()); + } + + std::string visitBlock(const Block* b) override { + std::ostringstream oss; + for (const auto* s : b->getChildren("statements")) { + oss << generate(s) << "\n"; + } + return trimTrailingNewline(oss.str()); + } + + std::string visitListLiteral(const ListLiteral* l) override { + std::ostringstream oss; + oss << "["; + auto elems = l->getChildren("elements"); + for (size_t i = 0; i < elems.size(); ++i) { + if (i > 0) oss << "; "; + oss << generate(elems[i]); + } + oss << "]"; + return oss.str(); + } + + std::string visitIndexAccess(const IndexAccess* a) override { + auto* target = a->getChild("target"); + auto* idx = a->getChild("index"); + return (target ? generate(target) : "") + "[" + (idx ? generate(idx) : "") + "]"; + } + + std::string visitMemberAccess(const MemberAccess* a) override { + auto* target = a->getChild("target"); + return (target ? generate(target) : "") + "." + a->memberName; + } + + std::string visitVariableReference(const VariableReference* v) override { + return v->variableName; + } + std::string visitIntegerLiteral(const IntegerLiteral* l) override { return std::to_string(l->value); } + std::string visitFloatLiteral(const FloatLiteral* l) override { return l->value; } + std::string visitStringLiteral(const StringLiteral* l) override { return "\"" + l->value + "\""; } + std::string visitBooleanLiteral(const BooleanLiteral* l) override { return l->value ? "true" : "false"; } + std::string visitNullLiteral(const NullLiteral*) override { return "null"; } + + std::string visitPrimitiveType(const PrimitiveType* t) override { + if (t->kind == "int") return "int"; + if (t->kind == "float" || t->kind == "double") return "float"; + if (t->kind == "string") return "string"; + if (t->kind == "bool") return "bool"; + if (t->kind == "void") return "unit"; + return t->kind; + } + std::string visitListType(const ListType* t) override { + auto* e = t->getChild("elementType"); + return (e ? generate(e) : "obj") + " list"; + } + std::string visitSetType(const SetType* t) override { + auto* e = t->getChild("elementType"); + return (e ? generate(e) : "obj") + " seq"; + } + std::string visitMapType(const MapType* t) override { + auto* k = t->getChild("keyType"); + auto* v = t->getChild("valueType"); + return "(" + std::string(k ? generate(k) : "obj") + " * " + + std::string(v ? generate(v) : "obj") + ") list"; + } + std::string visitTupleType(const TupleType* t) override { + std::ostringstream oss; + auto elems = t->getChildren("elementTypes"); + for (size_t i = 0; i < elems.size(); ++i) { + if (i > 0) oss << " * "; + oss << generate(elems[i]); + } + return oss.str(); + } + std::string visitArrayType(const ArrayType* t) override { + auto* e = t->getChild("elementType"); + return (e ? generate(e) : "obj") + " array"; + } + std::string visitOptionalType(const OptionalType* t) override { + auto* inner = t->getChild("innerType"); + return (inner ? generate(inner) : "obj") + " option"; + } + std::string visitCustomType(const CustomType* t) override { + if (t->typeName == "result") return "result"; + return t->typeName; + } + + // Subject 1 annotations not covered by SemannoAnnotationImpl. + std::string visitDerefStrategy(const DerefStrategy* a) override { + return commentPrefix() + SemannoEmitter::emit(a); + } + std::string visitOptimizationLock(const OptimizationLock* a) override { + return commentPrefix() + SemannoEmitter::emit(a); + } + std::string visitLangSpecific(const LangSpecific* a) override { + return commentPrefix() + SemannoEmitter::emit(a); + } + std::string visitDeallocateAnnotation(const DeallocateAnnotation* a) override { + return commentPrefix() + SemannoEmitter::emit(a); + } + std::string visitLifetimeAnnotation(const LifetimeAnnotation* a) override { + return commentPrefix() + SemannoEmitter::emit(a); + } + std::string visitReclaimAnnotation(const ReclaimAnnotation* a) override { + return commentPrefix() + SemannoEmitter::emit(a); + } + std::string visitOwnerAnnotation(const OwnerAnnotation* a) override { + return commentPrefix() + SemannoEmitter::emit(a); + } + std::string visitAllocateAnnotation(const AllocateAnnotation* a) override { + return commentPrefix() + SemannoEmitter::emit(a); + } + std::string visitHotColdAnnotation(const HotColdAnnotation* a) override { + return commentPrefix() + SemannoEmitter::emit(a); + } + std::string visitInlineAnnotation(const InlineAnnotation* a) override { + return commentPrefix() + SemannoEmitter::emit(a); + } + std::string visitPureAnnotation(const PureAnnotation* a) override { + return commentPrefix() + SemannoEmitter::emit(a); + } + std::string visitConstExprAnnotation(const ConstExprAnnotation* a) override { + return commentPrefix() + SemannoEmitter::emit(a); + } + + std::string visitNamespaceDeclaration(const ASTNode* node) override { + auto* ns = static_cast(node); + return "module " + ns->name; + } + + std::string visitEnumDeclaration(const ASTNode* node) override { + auto* en = static_cast(node); + std::ostringstream oss; + oss << "type " << en->name << " ="; + auto members = en->getChildren("members"); + if (members.empty()) { + oss << " | Unknown"; + return oss.str(); + } + for (const auto* m : members) { + auto* em = static_cast(m); + oss << "\n | " << em->name; + } + return oss.str(); + } + + std::string visitClassDeclaration(const ASTNode* node) override { + auto* cls = static_cast(node); + std::ostringstream oss; + oss << "type " << cls->name << " = {"; + auto fields = cls->getChildren("fields"); + if (!fields.empty()) oss << " "; + for (size_t i = 0; i < fields.size(); ++i) { + auto* v = static_cast(fields[i]); + if (i > 0) oss << "; "; + oss << v->name << ": obj"; + } + if (!fields.empty()) oss << " "; + oss << "}"; + return oss.str(); + } + +private: + static std::string trimTrailingNewline(std::string s) { + while (!s.empty() && (s.back() == '\n' || s.back() == '\r')) s.pop_back(); + return s; + } +}; diff --git a/editor/src/ast/Generator.h b/editor/src/ast/Generator.h index ba0748f..afad958 100644 --- a/editor/src/ast/Generator.h +++ b/editor/src/ast/Generator.h @@ -11,6 +11,7 @@ #include "GoGenerator.h" #include "KotlinGenerator.h" #include "CSharpGenerator.h" +#include "FSharpGenerator.h" #include "CGenerator.h" #include "WatGenerator.h" #include "CommonLispGenerator.h" diff --git a/editor/tests/step406_test.cpp b/editor/tests/step406_test.cpp new file mode 100644 index 0000000..9cf9c1e --- /dev/null +++ b/editor/tests/step406_test.cpp @@ -0,0 +1,205 @@ +// Step 406: F# Generator Tests (12 tests) + +#include "ast/FSharpGenerator.h" +#include "ast/Module.h" +#include "ast/Function.h" +#include "ast/Variable.h" +#include "ast/Parameter.h" +#include "ast/Expression.h" +#include "ast/Statement.h" +#include "ast/EnumNamespaceNodes.h" +#include "ast/ClassDeclaration.h" +#include "ast/Type.h" +#include "ast/Annotation.h" +#include "ast/AsyncNodes.h" +#include "Pipeline.h" + +#include +#include + +static int passed = 0, failed = 0; +#define TEST(name) { std::cout << " " << #name << "... "; } +#define PASS() { std::cout << "PASS\n"; ++passed; } +#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; } +#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {} + +void test_generate_let_function() { + TEST(generate_let_function); + Function fn("fn1", "add"); + fn.addChild("parameters", new Parameter("p1", "x")); + fn.addChild("parameters", new Parameter("p2", "y")); + auto* ret = new Return(); + ret->id = "ret1"; + ret->setChild("value", new BinaryOperation("b1", "+")); + fn.addChild("body", ret); + + FSharpGenerator gen; + std::string out = gen.generate(&fn); + CHECK(out.find("let add x y =") != std::string::npos, "expected let function signature"); + PASS(); +} + +void test_generate_mutable_variable() { + TEST(generate_mutable_variable); + Variable v("v1", "counter"); + auto* mut = new MutAnnotation(); + mut->id = "m1"; + v.addChild("annotations", mut); + + FSharpGenerator gen; + std::string out = gen.generate(&v); + CHECK(out.find("let mutable counter") != std::string::npos, "expected mutable variable"); + PASS(); +} + +void test_generate_discriminated_union() { + TEST(generate_discriminated_union); + EnumDeclaration en("e1", "Shape"); + en.addChild("members", new EnumMember("c1", "Circle")); + en.addChild("members", new EnumMember("c2", "Rectangle")); + + FSharpGenerator gen; + std::string out = gen.generate(&en); + CHECK(out.find("type Shape =") != std::string::npos, "expected type declaration"); + CHECK(out.find("| Circle") != std::string::npos, "expected Circle case"); + CHECK(out.find("| Rectangle") != std::string::npos, "expected Rectangle case"); + PASS(); +} + +void test_generate_record_class() { + TEST(generate_record_class); + ClassDeclaration cls("c1", "Person"); + cls.addChild("fields", new Variable("f1", "name")); + cls.addChild("fields", new Variable("f2", "age")); + + FSharpGenerator gen; + std::string out = gen.generate(&cls); + CHECK(out.find("type Person = {") != std::string::npos, "expected record type"); + CHECK(out.find("name: obj") != std::string::npos, "expected name field"); + CHECK(out.find("age: obj") != std::string::npos, "expected age field"); + PASS(); +} + +void test_generate_async_function() { + TEST(generate_async_function); + AsyncFunction fn("a1", "fetchData"); + fn.addChild("body", new Return()); + + FSharpGenerator gen; + std::string out = gen.generate(&fn); + CHECK(out.find("let fetchData = async {") != std::string::npos, "expected async function form"); + PASS(); +} + +void test_generate_module_namespace() { + TEST(generate_module_namespace); + NamespaceDeclaration ns("n1", "MyApp.Core"); + FSharpGenerator gen; + std::string out = gen.generate(&ns); + CHECK(out == "module MyApp.Core", "expected module declaration"); + PASS(); +} + +void test_generate_if_statement() { + TEST(generate_if_statement); + IfStatement ifs; + ifs.id = "if1"; + ifs.setChild("condition", new BooleanLiteral("b1", true)); + auto* thenRet = new Return(); + thenRet->id = "ret1"; + thenRet->setChild("value", new StringLiteral("s1", "yes")); + ifs.addChild("thenBranch", thenRet); + + FSharpGenerator gen; + std::string out = gen.generate(&ifs); + CHECK(out.find("if true then") != std::string::npos, "expected if then"); + PASS(); +} + +void test_generate_pipeline_call_marker() { + TEST(generate_pipeline_call_marker); + FunctionCall call; + call.id = "call1"; + call.functionName = "pipeline"; + FSharpGenerator gen; + std::string out = gen.generate(&call); + CHECK(out.find("|>") != std::string::npos, "expected pipeline operator"); + PASS(); +} + +void test_type_mapping() { + TEST(type_mapping); + PrimitiveType i("t1", "int"); + PrimitiveType s("t2", "string"); + PrimitiveType v("t3", "void"); + OptionalType opt; + opt.id = "opt1"; + opt.setChild("innerType", new CustomType("ct1", "result")); + + FSharpGenerator gen; + CHECK(gen.generate(&i) == "int", "int mapping"); + CHECK(gen.generate(&s) == "string", "string mapping"); + CHECK(gen.generate(&v) == "unit", "void->unit mapping"); + CHECK(gen.generate(&opt) == "result option", "optional mapping"); + PASS(); +} + +void test_comment_prefix() { + TEST(comment_prefix); + FSharpGenerator gen; + CHECK(gen.commentPrefix() == "// ", "expected // comment prefix"); + PASS(); +} + +void test_semanno_annotation_output() { + TEST(semanno_annotation_output); + Function fn("f1", "compute"); + auto* c = new ComplexityAnnotation(); + c->id = "a1"; + c->timeComplexity = "O(n)"; + fn.addChild("annotations", c); + + FSharpGenerator gen; + std::string out = gen.generate(&fn); + CHECK(out.find("@semanno:complexity") != std::string::npos, "expected semanno complexity output"); + PASS(); +} + +void test_pipeline_generate_routing() { + TEST(pipeline_generate_routing); + Module mod; + mod.id = "m1"; + mod.name = "demo"; + auto* fn = new Function("f1", "run"); + mod.addChild("functions", fn); + + Pipeline p; + std::string o1 = p.generate(&mod, "fsharp"); + std::string o2 = p.generate(&mod, "f#"); + std::string o3 = p.generate(&mod, "fs"); + CHECK(o1.find("let run") != std::string::npos, "fsharp route"); + CHECK(o2.find("let run") != std::string::npos, "f# route"); + CHECK(o3.find("let run") != std::string::npos, "fs route"); + PASS(); +} + +int main() { + std::cout << "Step 406: F# Generator Tests\n"; + + test_generate_let_function(); // 1 + test_generate_mutable_variable(); // 2 + test_generate_discriminated_union();// 3 + test_generate_record_class(); // 4 + test_generate_async_function(); // 5 + test_generate_module_namespace(); // 6 + test_generate_if_statement(); // 7 + test_generate_pipeline_call_marker();// 8 + test_type_mapping(); // 9 + test_comment_prefix(); // 10 + test_semanno_annotation_output(); // 11 + test_pipeline_generate_routing(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) + << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 8434691..c379e23 100644 --- a/progress.md +++ b/progress.md @@ -3674,6 +3674,57 @@ and pipe-operator chains. - `editor/src/MCPServer.h` (`1679` > `600`) - `editor/src/HeadlessAgentRPCHandler.h` (`2623` > `600`) +### Step 406: F# Generator +**Status:** PASS (12/12 tests) + +Added a dedicated F# generator with Semanno annotation emission support and +pipeline generate-route aliases (`fsharp`, `f#`, `fs`). Outputs functional +`let` forms, async computation expressions, discriminated-union style enum +output, record-style class output, and F#-style type mappings. + +**Files created:** +- `editor/src/ast/FSharpGenerator.h` — F# generation support: + - module/function/variable generation using `let` + - mutable variable output from `MutAnnotation` + - async output: `let name = async { ... }` + - namespace/module output (`module Name`) + - union output (`type Name = | Case...`) + - record output (`type Name = { field: obj; ... }`) + - pipeline marker generation (`|> pipeline`) + - type mapping for `int`, `float`, `string`, `bool`, `unit`, `option`, `result` + - Semanno output for subject 1 and extended annotation families +- `editor/tests/step406_test.cpp` — 12 tests covering: + 1. `let` function generation + 2. mutable variable generation + 3. discriminated union generation + 4. record/class generation + 5. async function generation + 6. module namespace generation + 7. if-statement generation + 8. pipeline marker generation + 9. type mapping behavior + 10. comment prefix (`// `) + 11. Semanno annotation output + 12. pipeline generate routing for `fsharp` / `f#` / `fs` + +**Files modified:** +- `editor/src/ast/Generator.h` — include `FSharpGenerator.h` +- `editor/src/Pipeline.h` — generate routing for `fsharp`, `f#`, `fs` +- `editor/CMakeLists.txt` — `step406_test` target + +**Verification run:** +- `step406_test` — PASS (12/12) new step coverage +- `step405_test` — PASS (12/12) regression coverage +- `step404_test` — PASS (8/8) regression coverage + +**Architecture gate check:** +- `editor/src/ast/FSharpGenerator.h` within header-size limit (`348` <= `600`) +- `editor/tests/step406_test.cpp` within test-file size guidance (`205` lines) +- `editor/src/Pipeline.h` within header-size limit (`240` <= `600`) +- Legacy oversized headers persist: + - `editor/src/MCPServer.h` (`1679` > `600`) + - `editor/src/HeadlessAgentRPCHandler.h` (`2623` > `600`) + # Roadmap Planning — Sprints 12-25+ ## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)