Step 366: Add WAT generator and pipeline generation route
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
|
||||
@@ -12,3 +12,4 @@
|
||||
#include "KotlinGenerator.h"
|
||||
#include "CSharpGenerator.h"
|
||||
#include "CGenerator.h"
|
||||
#include "WatGenerator.h"
|
||||
|
||||
219
editor/src/ast/WatGenerator.h
Normal file
219
editor/src/ast/WatGenerator.h
Normal 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";
|
||||
}
|
||||
}
|
||||
};
|
||||
178
editor/tests/step366_test.cpp
Normal file
178
editor/tests/step366_test.cpp
Normal 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;
|
||||
}
|
||||
42
progress.md
42
progress.md
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user