Step 366: Add WAT generator and pipeline generation route

This commit is contained in:
Bill
2026-02-16 09:53:33 -07:00
parent 219b828030
commit ccf21833f7
6 changed files with 452 additions and 0 deletions

View File

@@ -2173,4 +2173,13 @@ target_link_libraries(step365_test PRIVATE
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
add_executable(step366_test tests/step366_test.cpp)
target_include_directories(step366_test PRIVATE src)
target_link_libraries(step366_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

@@ -166,6 +166,9 @@ public:
} else if (language == "c") {
CGenerator gen;
return gen.generate(ast);
} else if (language == "wat" || language == "wasm") {
WatGenerator gen;
return gen.generate(ast);
}
return "";
}

View File

@@ -12,3 +12,4 @@
#include "KotlinGenerator.h"
#include "CSharpGenerator.h"
#include "CGenerator.h"
#include "WatGenerator.h"

View File

@@ -0,0 +1,219 @@
#pragma once
#include "CppGenerator.h"
#include "Import.h"
class WatGenerator : public CppGenerator {
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\n";
for (auto* imp : module->getChildren("imports")) {
if (imp->conceptType == "Import") {
oss << " " << emitImportWat(static_cast<const Import*>(imp)) << "\n";
}
}
for (auto* stmt : module->getChildren("statements")) {
oss << " " << generate(stmt) << "\n";
}
for (auto* var : module->getChildren("variables")) {
oss << " " << "(global $" << static_cast<Variable*>(var)->name
<< " (mut i32) (i32.const 0))\n";
}
for (auto* fn : module->getChildren("functions")) {
oss << " " << generate(fn) << "\n";
}
oss << ")";
return oss.str();
}
std::string visitFunction(const Function* function) override {
std::ostringstream oss;
oss << "(func $" << sanitizeName(function->name);
for (auto* pNode : function->getChildren("parameters")) {
auto* p = static_cast<const Parameter*>(pNode);
auto* t = p->getChild("type");
oss << " (param $" << sanitizeName(p->name) << " "
<< watType(t) << ")";
}
auto* ret = function->getChild("returnType");
if (ret && watType(ret) != "") {
oss << " (result " << watType(ret) << ")";
}
auto annos = function->getChildren("annotations");
if (!annos.empty()) {
for (auto* a : annos) {
oss << " " << generate(a);
}
}
auto body = function->getChildren("body");
if (!body.empty()) {
for (auto* s : body) {
oss << " " << generate(s);
}
}
oss << ")";
return oss.str();
}
std::string visitBinaryOperation(const BinaryOperation* binOp) override {
std::string op = "i32.add";
if (binOp->op == "-" || binOp->op == "sub") op = "i32.sub";
else if (binOp->op == "*" || binOp->op == "mul") op = "i32.mul";
std::ostringstream oss;
emitOperand(oss, binOp->getChild("left"));
oss << " ";
emitOperand(oss, binOp->getChild("right"));
oss << " " << op;
return oss.str();
}
std::string visitFunctionCall(const FunctionCall* call) override {
std::ostringstream oss;
for (auto* arg : call->getChildren("arguments")) {
emitOperand(oss, arg);
oss << " ";
}
oss << "call $" << sanitizeName(call->functionName);
return oss.str();
}
std::string visitExpressionStatement(const ExpressionStatement* stmt) override {
auto* expr = stmt->getChild("expression");
return expr ? generate(expr) : "nop";
}
std::string visitIfStatement(const IfStatement* stmt) override {
std::ostringstream oss;
oss << "(if";
if (auto* cond = stmt->getChild("condition")) {
oss << " (" << generate(cond) << ")";
}
oss << " (then";
for (auto* t : stmt->getChildren("thenBranch")) {
oss << " " << generate(t);
}
oss << ")";
auto elseBranch = stmt->getChildren("elseBranch");
if (!elseBranch.empty()) {
oss << " (else";
for (auto* e : elseBranch) oss << " " << generate(e);
oss << ")";
}
oss << ")";
return oss.str();
}
std::string visitBlock(const Block* block) override {
std::ostringstream oss;
oss << "(block";
for (auto* s : block->getChildren("statements")) {
oss << " " << generate(s);
}
oss << ")";
return oss.str();
}
std::string visitVariableReference(const VariableReference* ref) override {
return "local.get $" + sanitizeName(ref->variableName);
}
std::string visitIntegerLiteral(const IntegerLiteral* lit) override {
return "i32.const " + std::to_string(lit->value);
}
std::string visitFloatLiteral(const FloatLiteral* lit) override {
return "f32.const " + lit->value;
}
std::string visitBooleanLiteral(const BooleanLiteral* lit) override {
return std::string("i32.const ") + (lit->value ? "1" : "0");
}
std::string visitPragmaDirective(const ASTNode* node) override {
auto* p = static_cast<const PragmaDirective*>(node);
if (p->directive == "export") return "(export \"f\" (func $f))";
if (p->directive == "memory") return "(memory 1)";
if (p->directive == "table") return "(table 1 funcref)";
return "(; pragma " + p->directive + " ;)";
}
std::string visitOwnerAnnotation(const OwnerAnnotation* annotation) override {
return ";; @owner(" + annotation->strategy + ")";
}
private:
static std::string emitImportWat(const Import* imp) {
std::string moduleName = imp->moduleName;
std::string importName = imp->alias.empty() ? imp->moduleName : imp->alias;
auto dot = moduleName.find('.');
if (dot != std::string::npos) {
importName = moduleName.substr(dot + 1);
moduleName = moduleName.substr(0, dot);
}
return "(import \"" + moduleName + "\" \"" + importName + "\" (func $" +
sanitizeName(importName) + "))";
}
static std::string sanitizeName(const std::string& name) {
if (name.empty()) return "fn";
std::string out = name;
for (auto& c : out) {
if (c == '$' || c == '-' || c == ':') c = '_';
}
return out;
}
static std::string watType(const ASTNode* typeNode) {
if (!typeNode) return "";
std::string kind;
if (typeNode->conceptType == "PrimitiveType") {
kind = static_cast<const PrimitiveType*>(typeNode)->kind;
} else if (typeNode->conceptType == "CustomType") {
kind = static_cast<const CustomType*>(typeNode)->typeName;
}
if (kind == "int" || kind == "bool" || kind == "i32") return "i32";
if (kind == "long" || kind == "i64") return "i64";
if (kind == "float" || kind == "f32") return "f32";
if (kind == "double" || kind == "f64") return "f64";
if (kind == "void") return "";
return kind.empty() ? "i32" : kind;
}
static void emitOperand(std::ostringstream& oss, const ASTNode* operand) {
if (!operand) {
oss << "i32.const 0";
return;
}
if (operand->conceptType == "VariableReference") {
oss << "local.get $" << sanitizeName(static_cast<const VariableReference*>(operand)->variableName);
} else if (operand->conceptType == "IntegerLiteral") {
oss << "i32.const " << static_cast<const IntegerLiteral*>(operand)->value;
} else if (operand->conceptType == "FloatLiteral") {
oss << "f32.const " << static_cast<const FloatLiteral*>(operand)->value;
} else {
oss << "i32.const 0";
}
}
};

View File

@@ -0,0 +1,178 @@
// Step 366: WAT Generator (12 tests)
#include <cassert>
#include <iostream>
#include <string>
#include "Pipeline.h"
#include "ast/WatGenerator.h"
#include "ast/WatParser.h"
static int parenBalance(const std::string& s) {
int d = 0;
for (char c : s) {
if (c == '(') ++d;
if (c == ')') --d;
}
return d;
}
int main() {
int passed = 0;
// Test 1: function generation
{
WatGenerator gen;
Function fn("f1", "add");
auto* p1 = new Parameter("p1", "x");
p1->setChild("type", new PrimitiveType("t1", "int"));
fn.addChild("parameters", p1);
fn.setChild("returnType", new PrimitiveType("rt1", "int"));
auto out = gen.generate(&fn);
assert(out.find("(func $add") != std::string::npos);
assert(out.find("(param $x i32)") != std::string::npos);
assert(out.find("(result i32)") != std::string::npos);
std::cout << "Test 1 PASSED: function generation\n";
passed++;
}
// Test 2: S-expression formatting for module
{
WatGenerator gen;
Module mod("m1", "wat_mod", "wat");
auto out = gen.generate(&mod);
assert(out.find("(module") == 0);
assert(out.back() == ')');
std::cout << "Test 2 PASSED: module s-expression formatting\n";
passed++;
}
// Test 3: type mapping
{
WatGenerator gen;
Function fn("f1", "types");
auto* p = new Parameter("p1", "a");
p->setChild("type", new PrimitiveType("t1", "double"));
fn.addChild("parameters", p);
fn.setChild("returnType", new PrimitiveType("rt", "long"));
auto out = gen.generate(&fn);
assert(out.find("f64") != std::string::npos);
assert(out.find("i64") != std::string::npos);
std::cout << "Test 3 PASSED: type mapping\n";
passed++;
}
// Test 4: arithmetic to stack instruction
{
WatGenerator gen;
BinaryOperation add;
add.id = "b1";
add.op = "+";
add.setChild("left", new VariableReference("vr1", "x"));
add.setChild("right", new VariableReference("vr2", "y"));
auto out = gen.generate(&add);
assert(out.find("i32.add") != std::string::npos);
assert(out.find("local.get $x") != std::string::npos);
std::cout << "Test 4 PASSED: arithmetic stack lowering\n";
passed++;
}
// Test 5: if/else to WAT if
{
WatGenerator gen;
IfStatement stmt;
stmt.id = "if1";
stmt.setChild("condition", new BooleanLiteral("b1", true));
stmt.addChild("thenBranch", new IntegerLiteral("i1", 1));
stmt.addChild("elseBranch", new IntegerLiteral("i2", 0));
auto out = gen.generate(&stmt);
assert(out.find("(if") != std::string::npos);
assert(out.find("(then") != std::string::npos);
assert(out.find("(else") != std::string::npos);
std::cout << "Test 5 PASSED: if/else generation\n";
passed++;
}
// Test 6: function call to call instruction
{
WatGenerator gen;
FunctionCall call;
call.id = "c1";
call.functionName = "helper";
auto out = gen.generate(&call);
assert(out.find("call $helper") != std::string::npos);
std::cout << "Test 6 PASSED: function call generation\n";
passed++;
}
// Test 7: export declaration
{
WatGenerator gen;
PragmaDirective exp("p1", "export");
auto out = gen.generate(&exp);
assert(out.find("(export") != std::string::npos);
std::cout << "Test 7 PASSED: export generation\n";
passed++;
}
// Test 8: import declaration
{
WatGenerator gen;
Module mod("m1", "imports", "wat");
mod.addChild("imports", new Import("i1", "env.print", "runtime", ""));
auto out = gen.generate(&mod);
assert(out.find("(import") != std::string::npos);
assert(out.find("\"env\"") != std::string::npos);
std::cout << "Test 8 PASSED: import generation\n";
passed++;
}
// Test 9: Semanno comments with ;; prefix
{
WatGenerator gen;
Function fn("f1", "owned");
fn.addChild("annotations", new OwnerAnnotation("a1", "Manual"));
auto out = gen.generate(&fn);
assert(out.find(";; @owner(Manual)") != std::string::npos);
std::cout << "Test 9 PASSED: semanno-style comment prefix\n";
passed++;
}
// Test 10: cross-language pipeline Python -> WAT
{
Pipeline p;
std::string py = "def add(x, y):\n return x + y\n";
auto result = p.run(py, "python", "wat");
assert(result.success);
assert(result.generatedCode.find("(module") != std::string::npos);
std::cout << "Test 10 PASSED: Python->WAT pipeline route\n";
passed++;
}
// Test 11: round-trip parse -> generate -> parse
{
std::string wat = "(module (func $add (param $x i32) (param $y i32) (result i32) i32.add))";
auto parsed = WatParser::parseWat(wat);
WatGenerator gen;
std::string out = gen.generate(parsed.get());
auto reparsed = WatParser::parseWat(out);
assert(!reparsed->getChildren("functions").empty());
std::cout << "Test 11 PASSED: parse/generate/parse roundtrip\n";
passed++;
}
// Test 12: parentheses balanced in generated module
{
WatGenerator gen;
Module mod("m1", "bal", "wat");
Function* fn = new Function("f1", "noop");
mod.addChild("functions", fn);
auto out = gen.generate(&mod);
assert(parenBalance(out) == 0);
std::cout << "Test 12 PASSED: balanced parentheses\n";
passed++;
}
std::cout << "\nResults: " << passed << "/12\n";
assert(passed == 12);
return 0;
}

View File

@@ -2244,6 +2244,48 @@ into the shared parsing pipeline.
**Architecture gate check:**
- `editor/src/ast/WatParser.h` is within header size limit (`231` lines)
### Step 366: WAT Generator
**Status:** PASS (12/12 tests)
Added a dedicated WAT generator and integrated `wat/wasm` generation routing in
the shared pipeline.
**Files created:**
- `editor/src/ast/WatGenerator.h` — WAT generation support:
- module emission in s-expression format
- function emission with `(param ...)` / `(result ...)`
- primitive type mapping to WAT types (`i32`, `i64`, `f32`, `f64`)
- instruction emission for arithmetic, calls, if/else, and block constructs
- import emission and pragma-driven memory/table/export output
- annotation comment output using WAT prefix (`;; `)
- `editor/tests/step366_test.cpp` — 12 tests covering:
1. function generation
2. module s-expression formatting
3. type mapping
4. arithmetic lowering to stack ops
5. if/else output
6. function call output
7. export output
8. import output
9. annotation comment prefix
10. Python -> WAT pipeline route
11. parse->generate->parse roundtrip
12. balanced parentheses invariant
**Files modified:**
- `editor/src/ast/Generator.h` — include `WatGenerator.h`
- `editor/src/Pipeline.h` — add generate routing for `\"wat\"` and `\"wasm\"`
- `editor/CMakeLists.txt``step366_test` target
**Verification run:**
- `step366_test` — PASS (12/12) new step coverage
- `step365_test` — PASS (12/12) regression coverage
- `step364_test` — PASS (8/8) regression coverage
- `step363_test` — PASS (12/12) regression coverage
**Architecture gate check:**
- `editor/src/ast/WatGenerator.h` is within header size limit (`219` lines)
# Roadmap Planning — Sprints 12-25+
## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)