From 95922f1ec64f686e80242e3d75e4a83749b90796 Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 16 Feb 2026 20:06:47 -0700 Subject: [PATCH] Complete step460 assembly AST nodes with serialization tests --- editor/CMakeLists.txt | 9 ++ editor/src/ast/AssemblyNodes.h | 200 +++++++++++++++++++++++++++++++++ editor/tests/step460_test.cpp | 160 ++++++++++++++++++++++++++ progress.md | 33 ++++++ 4 files changed, 402 insertions(+) create mode 100644 editor/src/ast/AssemblyNodes.h create mode 100644 editor/tests/step460_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index b9e23f7..eed3c0e 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3019,4 +3019,13 @@ target_link_libraries(step459_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step460_test tests/step460_test.cpp) +target_include_directories(step460_test PRIVATE src) +target_link_libraries(step460_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/ast/AssemblyNodes.h b/editor/src/ast/AssemblyNodes.h new file mode 100644 index 0000000..ca13253 --- /dev/null +++ b/editor/src/ast/AssemblyNodes.h @@ -0,0 +1,200 @@ +#pragma once + +// Step 460: Assembly AST Nodes +// Node types for assembly instructions, labels, directives, registers, and +// memory operands. These can be attached under Function nodes as statement-like +// children for assembly-backed function bodies. + +#include "ASTNode.h" + +#include +#include +#include + +using json = nlohmann::json; + +enum class AssemblyDirectiveType { + Data, + Text, + Global, + Section, + Byte, + Word, + Align, + Unknown +}; + +inline std::string directiveTypeToString(AssemblyDirectiveType t) { + switch (t) { + case AssemblyDirectiveType::Data: return ".data"; + case AssemblyDirectiveType::Text: return ".text"; + case AssemblyDirectiveType::Global: return ".global"; + case AssemblyDirectiveType::Section: return ".section"; + case AssemblyDirectiveType::Byte: return ".byte"; + case AssemblyDirectiveType::Word: return ".word"; + case AssemblyDirectiveType::Align: return ".align"; + case AssemblyDirectiveType::Unknown: return ".unknown"; + } + return ".unknown"; +} + +inline AssemblyDirectiveType directiveTypeFromString(const std::string& s) { + if (s == ".data") return AssemblyDirectiveType::Data; + if (s == ".text") return AssemblyDirectiveType::Text; + if (s == ".global") return AssemblyDirectiveType::Global; + if (s == ".section") return AssemblyDirectiveType::Section; + if (s == ".byte") return AssemblyDirectiveType::Byte; + if (s == ".word") return AssemblyDirectiveType::Word; + if (s == ".align") return AssemblyDirectiveType::Align; + return AssemblyDirectiveType::Unknown; +} + +class AssemblyRegister : public ASTNode { +public: + std::string name; // rax, eax, x0, r0, etc. + int sizeBits = 0; // 8/16/32/64 + + AssemblyRegister() { conceptType = "AssemblyRegister"; } + AssemblyRegister(std::string n, int bits) : name(std::move(n)), sizeBits(bits) { + conceptType = "AssemblyRegister"; + } +}; + +class AssemblyMemoryOperand : public ASTNode { +public: + std::string baseRegister; // rbp + int offset = 0; // -8 + std::string indexRegister; // rbx + int scale = 1; // 1/2/4/8 + + AssemblyMemoryOperand() { conceptType = "AssemblyMemoryOperand"; } +}; + +class AssemblyInstruction : public ASTNode { +public: + std::string opcode; // mov/add/sub... + std::vector operands; // textual fallback operands + // Optional structured operand pieces for richer addressing semantics. + std::vector registers; + std::vector memoryOperands; + + AssemblyInstruction() { conceptType = "AssemblyInstruction"; } + AssemblyInstruction(std::string op, std::vector ops) + : opcode(std::move(op)), operands(std::move(ops)) { + conceptType = "AssemblyInstruction"; + } +}; + +class AssemblyLabel : public ASTNode { +public: + std::string name; + bool isGlobal = false; + + AssemblyLabel() { conceptType = "AssemblyLabel"; } + AssemblyLabel(std::string n, bool global) : name(std::move(n)), isGlobal(global) { + conceptType = "AssemblyLabel"; + } +}; + +class AssemblyDirective : public ASTNode { +public: + AssemblyDirectiveType type = AssemblyDirectiveType::Unknown; + std::string value; // .section name, .word literal value, etc. + + AssemblyDirective() { conceptType = "AssemblyDirective"; } + AssemblyDirective(AssemblyDirectiveType t, std::string v) + : type(t), value(std::move(v)) { + conceptType = "AssemblyDirective"; + } +}; + +inline json toJson(const AssemblyRegister& r) { + return { + {"concept", r.conceptType}, + {"name", r.name}, + {"sizeBits", r.sizeBits} + }; +} + +inline AssemblyRegister registerFromJson(const json& j) { + AssemblyRegister r; + r.name = j.value("name", ""); + r.sizeBits = j.value("sizeBits", 0); + return r; +} + +inline json toJson(const AssemblyMemoryOperand& m) { + return { + {"concept", m.conceptType}, + {"baseRegister", m.baseRegister}, + {"offset", m.offset}, + {"indexRegister", m.indexRegister}, + {"scale", m.scale} + }; +} + +inline AssemblyMemoryOperand memoryOperandFromJson(const json& j) { + AssemblyMemoryOperand m; + m.baseRegister = j.value("baseRegister", ""); + m.offset = j.value("offset", 0); + m.indexRegister = j.value("indexRegister", ""); + m.scale = j.value("scale", 1); + return m; +} + +inline json toJson(const AssemblyInstruction& i) { + json regs = json::array(); + for (const auto& r : i.registers) regs.push_back(toJson(r)); + json mems = json::array(); + for (const auto& m : i.memoryOperands) mems.push_back(toJson(m)); + return { + {"concept", i.conceptType}, + {"opcode", i.opcode}, + {"operands", i.operands}, + {"registers", regs}, + {"memoryOperands", mems} + }; +} + +inline AssemblyInstruction instructionFromJson(const json& j) { + AssemblyInstruction i; + i.opcode = j.value("opcode", ""); + i.operands = j.value("operands", std::vector{}); + if (j.contains("registers")) { + for (const auto& r : j["registers"]) i.registers.push_back(registerFromJson(r)); + } + if (j.contains("memoryOperands")) { + for (const auto& m : j["memoryOperands"]) i.memoryOperands.push_back(memoryOperandFromJson(m)); + } + return i; +} + +inline json toJson(const AssemblyLabel& l) { + return { + {"concept", l.conceptType}, + {"name", l.name}, + {"isGlobal", l.isGlobal} + }; +} + +inline AssemblyLabel labelFromJson(const json& j) { + AssemblyLabel l; + l.name = j.value("name", ""); + l.isGlobal = j.value("isGlobal", false); + return l; +} + +inline json toJson(const AssemblyDirective& d) { + return { + {"concept", d.conceptType}, + {"type", directiveTypeToString(d.type)}, + {"value", d.value} + }; +} + +inline AssemblyDirective directiveFromJson(const json& j) { + AssemblyDirective d; + d.type = directiveTypeFromString(j.value("type", ".unknown")); + d.value = j.value("value", ""); + return d; +} diff --git a/editor/tests/step460_test.cpp b/editor/tests/step460_test.cpp new file mode 100644 index 0000000..3b02955 --- /dev/null +++ b/editor/tests/step460_test.cpp @@ -0,0 +1,160 @@ +// Step 460: Assembly AST Nodes Tests (12 tests) + +#include "ast/AssemblyNodes.h" + +#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_instruction_node_fields() { + TEST(instruction_node_fields); + AssemblyInstruction ins("mov", {"rax", "rbx"}); + CHECK(ins.conceptType == "AssemblyInstruction", "wrong conceptType"); + CHECK(ins.opcode == "mov", "wrong opcode"); + CHECK(ins.operands.size() == 2, "wrong operand count"); + PASS(); +} + +void test_label_node_fields() { + TEST(label_node_fields); + AssemblyLabel label("main", true); + CHECK(label.conceptType == "AssemblyLabel", "wrong conceptType"); + CHECK(label.name == "main", "wrong label name"); + CHECK(label.isGlobal, "expected global label"); + PASS(); +} + +void test_directive_node_fields() { + TEST(directive_node_fields); + AssemblyDirective d(AssemblyDirectiveType::Text, ""); + CHECK(d.conceptType == "AssemblyDirective", "wrong conceptType"); + CHECK(d.type == AssemblyDirectiveType::Text, "wrong directive type"); + PASS(); +} + +void test_register_node_fields() { + TEST(register_node_fields); + AssemblyRegister r("rax", 64); + CHECK(r.conceptType == "AssemblyRegister", "wrong conceptType"); + CHECK(r.name == "rax", "wrong register name"); + CHECK(r.sizeBits == 64, "wrong register size"); + PASS(); +} + +void test_memory_operand_fields() { + TEST(memory_operand_fields); + AssemblyMemoryOperand m; + m.baseRegister = "rbp"; + m.offset = -8; + m.indexRegister = "rbx"; + m.scale = 4; + CHECK(m.conceptType == "AssemblyMemoryOperand", "wrong conceptType"); + CHECK(m.baseRegister == "rbp", "wrong base register"); + CHECK(m.offset == -8, "wrong offset"); + CHECK(m.scale == 4, "wrong scale"); + PASS(); +} + +void test_directive_type_string_roundtrip() { + TEST(directive_type_string_roundtrip); + auto s = directiveTypeToString(AssemblyDirectiveType::Global); + auto t = directiveTypeFromString(s); + CHECK(s == ".global", "wrong directive string"); + CHECK(t == AssemblyDirectiveType::Global, "wrong directive parse"); + PASS(); +} + +void test_register_json_roundtrip() { + TEST(register_json_roundtrip); + AssemblyRegister r("eax", 32); + auto j = toJson(r); + auto out = registerFromJson(j); + CHECK(out.name == "eax", "wrong register from json"); + CHECK(out.sizeBits == 32, "wrong register bits from json"); + PASS(); +} + +void test_memory_operand_json_roundtrip() { + TEST(memory_operand_json_roundtrip); + AssemblyMemoryOperand m; + m.baseRegister = "rax"; + m.offset = 8; + m.indexRegister = "rcx"; + m.scale = 2; + auto j = toJson(m); + auto out = memoryOperandFromJson(j); + CHECK(out.baseRegister == "rax", "wrong base register from json"); + CHECK(out.offset == 8, "wrong offset from json"); + CHECK(out.indexRegister == "rcx", "wrong index register from json"); + CHECK(out.scale == 2, "wrong scale from json"); + PASS(); +} + +void test_instruction_json_roundtrip_with_structured_operands() { + TEST(instruction_json_roundtrip_with_structured_operands); + AssemblyInstruction i("add", {"rax", "[rbp-8]"}); + i.registers.push_back(AssemblyRegister("rax", 64)); + AssemblyMemoryOperand m; + m.baseRegister = "rbp"; + m.offset = -8; + i.memoryOperands.push_back(m); + auto j = toJson(i); + auto out = instructionFromJson(j); + CHECK(out.opcode == "add", "wrong opcode from json"); + CHECK(out.operands.size() == 2, "wrong operand count from json"); + CHECK(out.registers.size() == 1, "wrong register count from json"); + CHECK(out.memoryOperands.size() == 1, "wrong mem operand count from json"); + PASS(); +} + +void test_label_json_roundtrip() { + TEST(label_json_roundtrip); + AssemblyLabel l("loop_start", false); + auto j = toJson(l); + auto out = labelFromJson(j); + CHECK(out.name == "loop_start", "wrong label from json"); + CHECK(!out.isGlobal, "wrong global flag from json"); + PASS(); +} + +void test_directive_json_roundtrip() { + TEST(directive_json_roundtrip); + AssemblyDirective d(AssemblyDirectiveType::Align, "16"); + auto j = toJson(d); + auto out = directiveFromJson(j); + CHECK(out.type == AssemblyDirectiveType::Align, "wrong directive type from json"); + CHECK(out.value == "16", "wrong directive value from json"); + PASS(); +} + +void test_unknown_directive_parses_to_unknown() { + TEST(unknown_directive_parses_to_unknown); + auto t = directiveTypeFromString(".bogus"); + CHECK(t == AssemblyDirectiveType::Unknown, "expected unknown directive type"); + PASS(); +} + +int main() { + std::cout << "Step 460: Assembly AST Nodes Tests\n"; + + test_instruction_node_fields(); // 1 + test_label_node_fields(); // 2 + test_directive_node_fields(); // 3 + test_register_node_fields(); // 4 + test_memory_operand_fields(); // 5 + test_directive_type_string_roundtrip(); // 6 + test_register_json_roundtrip(); // 7 + test_memory_operand_json_roundtrip(); // 8 + test_instruction_json_roundtrip_with_structured_operands(); // 9 + test_label_json_roundtrip(); // 10 + test_directive_json_roundtrip(); // 11 + test_unknown_directive_parses_to_unknown(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) + << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 6e04c58..da9a06b 100644 --- a/progress.md +++ b/progress.md @@ -6032,3 +6032,36 @@ generation, confidence scoring, equivalence checks, and RPC/MCP surface. - deterministic confidence scoring and review gating - per-function/project translation reporting - RPC + MCP interfaces for transpilation pipeline operations + +### Step 460: Assembly AST Nodes +**Status:** PASS (12/12 tests) + +Introduces core assembly AST node model and per-node JSON serialization +round-trip helpers for instructions, labels, directives, registers, and +memory operands. + +**Files added:** +- `editor/src/ast/AssemblyNodes.h` — new assembly node definitions: + - `AssemblyInstruction` (opcode, operands, structured register/memory operands) + - `AssemblyLabel` (name, `isGlobal`) + - `AssemblyDirective` (`.data`, `.text`, `.global`, `.section`, `.byte`, `.word`, `.align`) + - `AssemblyRegister` (name + bit width) + - `AssemblyMemoryOperand` (base/index/scale/offset addressing components) + - directive enum/string conversion helpers + - JSON serialization/deserialization helpers for all node types +- `editor/tests/step460_test.cpp` — 12 tests covering: + - node construction and field integrity for all assembly node types + - directive enum/string mapping + - JSON round-trip for each node type + - structured operand round-trip in instructions + - unknown directive fallback behavior +- `editor/CMakeLists.txt` — `step460_test` target + +**Verification run:** +- `cmake --build editor/build-native --target step460_test` — PASS +- `./editor/build-native/step460_test` — PASS (12/12) +- `./editor/build-native/step459_test` — PASS (8/8) regression coverage + +**Architecture gate check:** +- `editor/src/ast/AssemblyNodes.h` within header-size limit (`200` <= `600`) +- `editor/tests/step460_test.cpp` within test-file size guidance (`160` lines)