Step 406: add F# generator and pipeline generate routing
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
348
editor/src/ast/FSharpGenerator.h
Normal file
348
editor/src/ast/FSharpGenerator.h
Normal 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;
|
||||
}
|
||||
};
|
||||
@@ -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"
|
||||
|
||||
205
editor/tests/step406_test.cpp
Normal file
205
editor/tests/step406_test.cpp
Normal 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;
|
||||
}
|
||||
51
progress.md
51
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)
|
||||
|
||||
Reference in New Issue
Block a user