Step 406: add F# generator and pipeline generate routing

This commit is contained in:
Bill
2026-02-16 15:26:23 -07:00
parent a7597a649c
commit 9aab804687
6 changed files with 617 additions and 0 deletions

View File

@@ -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)

View File

@@ -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);

View File

@@ -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<FSharpGenerator> {
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<const Parameter*>(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<const AsyncFunction*>(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<const Parameter*>(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<const NamespaceDeclaration*>(node);
return "module " + ns->name;
}
std::string visitEnumDeclaration(const ASTNode* node) override {
auto* en = static_cast<const EnumDeclaration*>(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<const EnumMember*>(m);
oss << "\n | " << em->name;
}
return oss.str();
}
std::string visitClassDeclaration(const ASTNode* node) override {
auto* cls = static_cast<const ClassDeclaration*>(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<const Variable*>(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;
}
};

View File

@@ -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"

View File

@@ -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 <iostream>
#include <string>
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;
}

View File

@@ -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)