Complete step460 assembly AST nodes with serialization tests
This commit is contained in:
@@ -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)
|
||||
|
||||
200
editor/src/ast/AssemblyNodes.h
Normal file
200
editor/src/ast/AssemblyNodes.h
Normal file
@@ -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 <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<std::string> operands; // textual fallback operands
|
||||
// Optional structured operand pieces for richer addressing semantics.
|
||||
std::vector<AssemblyRegister> registers;
|
||||
std::vector<AssemblyMemoryOperand> memoryOperands;
|
||||
|
||||
AssemblyInstruction() { conceptType = "AssemblyInstruction"; }
|
||||
AssemblyInstruction(std::string op, std::vector<std::string> 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<std::string>{});
|
||||
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;
|
||||
}
|
||||
160
editor/tests/step460_test.cpp
Normal file
160
editor/tests/step460_test.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
// Step 460: Assembly AST Nodes Tests (12 tests)
|
||||
|
||||
#include "ast/AssemblyNodes.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
33
progress.md
33
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)
|
||||
|
||||
Reference in New Issue
Block a user