Data update
This commit is contained in:
parent
72eb4943cb
commit
4d5544505c
2347 changed files with 62432 additions and 16731 deletions
|
|
@ -0,0 +1,502 @@
|
|||
// Required Node.js modules
|
||||
const fs = require('fs');
|
||||
const path = require('path'); // Useful for joining paths if needed
|
||||
|
||||
// --- Constants ---
|
||||
const WORD_SIZE = 4; // In bytes
|
||||
|
||||
// --- Enums (JavaScript Object Equivalents) ---
|
||||
|
||||
const Mnemonic = {
|
||||
NONE: { ordinal: 0, name: 'NONE' },
|
||||
FETCH: { ordinal: 1, name: 'FETCH' },
|
||||
STORE: { ordinal: 2, name: 'STORE' },
|
||||
PUSH: { ordinal: 3, name: 'PUSH' },
|
||||
ADD: { ordinal: 4, name: 'ADD' },
|
||||
SUB: { ordinal: 5, name: 'SUB' },
|
||||
MUL: { ordinal: 6, name: 'MUL' },
|
||||
DIV: { ordinal: 7, name: 'DIV' },
|
||||
MOD: { ordinal: 8, name: 'MOD' },
|
||||
LT: { ordinal: 9, name: 'LT' },
|
||||
GT: { ordinal: 10, name: 'GT' },
|
||||
LE: { ordinal: 11, name: 'LE' },
|
||||
GE: { ordinal: 12, name: 'GE' },
|
||||
EQ: { ordinal: 13, name: 'EQ' },
|
||||
NE: { ordinal: 14, name: 'NE' },
|
||||
AND: { ordinal: 15, name: 'AND' },
|
||||
OR: { ordinal: 16, name: 'OR' },
|
||||
NEG: { ordinal: 17, name: 'NEG' },
|
||||
NOT: { ordinal: 18, name: 'NOT' },
|
||||
JMP: { ordinal: 19, name: 'JMP' },
|
||||
JZ: { ordinal: 20, name: 'JZ' },
|
||||
PRTC: { ordinal: 21, name: 'PRTC' },
|
||||
PRTS: { ordinal: 22, name: 'PRTS' },
|
||||
PRTI: { ordinal: 23, name: 'PRTI' },
|
||||
HALT: { ordinal: 24, name: 'HALT' },
|
||||
};
|
||||
|
||||
// Helper array to map ordinal back to Mnemonic object (for listCode)
|
||||
const MnemonicByOrdinal = Object.values(Mnemonic).sort((a, b) => a.ordinal - b.ordinal);
|
||||
|
||||
|
||||
const NodeType = {
|
||||
nd_None: { name: 'None', mnemonic: Mnemonic.NONE },
|
||||
nd_Ident: { name: 'Identifier', mnemonic: Mnemonic.NONE },
|
||||
nd_String: { name: 'String', mnemonic: Mnemonic.NONE },
|
||||
nd_Integer: { name: 'Integer', mnemonic: Mnemonic.NONE },
|
||||
nd_Sequence: { name: 'Sequence', mnemonic: Mnemonic.NONE },
|
||||
nd_If: { name: 'If', mnemonic: Mnemonic.NONE },
|
||||
nd_Prtc: { name: 'Prtc', mnemonic: Mnemonic.PRTC },
|
||||
nd_Prts: { name: 'Prts', mnemonic: Mnemonic.PRTS },
|
||||
nd_Prti: { name: 'Prti', mnemonic: Mnemonic.PRTI },
|
||||
nd_While: { name: 'While', mnemonic: Mnemonic.NONE },
|
||||
nd_Assign: { name: 'Assign', mnemonic: Mnemonic.NONE },
|
||||
nd_Negate: { name: 'Negate', mnemonic: Mnemonic.NEG },
|
||||
nd_Not: { name: 'Not', mnemonic: Mnemonic.NOT },
|
||||
nd_Mul: { name: 'Multiply', mnemonic: Mnemonic.MUL },
|
||||
nd_Div: { name: 'Divide', mnemonic: Mnemonic.DIV },
|
||||
nd_Mod: { name: 'Mod', mnemonic: Mnemonic.MOD },
|
||||
nd_Add: { name: 'Add', mnemonic: Mnemonic.ADD },
|
||||
nd_Sub: { name: 'Subtract', mnemonic: Mnemonic.SUB },
|
||||
nd_Lss: { name: 'Less', mnemonic: Mnemonic.LT },
|
||||
nd_Leq: { name: 'LessEqual', mnemonic: Mnemonic.LE },
|
||||
nd_Gtr: { name: 'Greater', mnemonic: Mnemonic.GT },
|
||||
nd_Geq: { name: 'GreaterEqual', mnemonic: Mnemonic.GE },
|
||||
nd_Eql: { name: 'Equal', mnemonic: Mnemonic.EQ },
|
||||
nd_Neq: { name: 'NotEqual', mnemonic: Mnemonic.NE },
|
||||
nd_And: { name: 'And', mnemonic: Mnemonic.AND },
|
||||
nd_Or: { name: 'Or', mnemonic: Mnemonic.OR },
|
||||
};
|
||||
|
||||
|
||||
// --- AST Node Class ---
|
||||
class Node {
|
||||
constructor(nt = null, left = null, right = null, value = null) {
|
||||
this.nt = nt;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
static makeNode(nodetype, left, right) {
|
||||
return new Node(nodetype, left, right, null);
|
||||
}
|
||||
|
||||
static makeNode1(nodetype, left) {
|
||||
return new Node(nodetype, left, null, null);
|
||||
}
|
||||
|
||||
static makeLeaf(nodetype, value) {
|
||||
return new Node(nodetype, null, null, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Code Generator State and Methods ---
|
||||
|
||||
let code = new Uint8Array(0); // Use Uint8Array for byte code
|
||||
const strToNodes = new Map();
|
||||
const stringPool = [];
|
||||
const variables = [];
|
||||
let stringCount = 0;
|
||||
let varCount = 0;
|
||||
|
||||
const unaryOps = [
|
||||
NodeType.nd_Negate, NodeType.nd_Not
|
||||
];
|
||||
const operators = [
|
||||
NodeType.nd_Mul, NodeType.nd_Div, NodeType.nd_Mod, NodeType.nd_Add, NodeType.nd_Sub,
|
||||
NodeType.nd_Lss, NodeType.nd_Leq, NodeType.nd_Gtr, NodeType.nd_Geq,
|
||||
NodeType.nd_Eql, NodeType.nd_Neq, NodeType.nd_And, NodeType.nd_Or
|
||||
];
|
||||
|
||||
// State for reading AST from lines
|
||||
let inputLines = [];
|
||||
let currentLineIndex = 0;
|
||||
|
||||
|
||||
function appendToCode(b) {
|
||||
const newCode = new Uint8Array(code.length + 1);
|
||||
newCode.set(code); // Copy existing bytes
|
||||
newCode[code.length] = b & 0xff; // Add new byte (ensure it's 0-255)
|
||||
code = newCode;
|
||||
}
|
||||
|
||||
function emitByte(m) {
|
||||
appendToCode(m.ordinal);
|
||||
}
|
||||
|
||||
function emitWord(n) {
|
||||
// Ensure n is treated as a 32-bit integer (signed or unsigned depends on context)
|
||||
// Emit bytes in big-endian order
|
||||
appendToCode((n >> 24) & 0xff);
|
||||
appendToCode((n >> 16) & 0xff);
|
||||
appendToCode((n >> 8) & 0xff);
|
||||
appendToCode(n & 0xff);
|
||||
}
|
||||
|
||||
function emitWordAt(pos, n) {
|
||||
if (pos + WORD_SIZE > code.length) {
|
||||
throw new Error(`Emit word out of bounds at position ${pos}`);
|
||||
}
|
||||
// Ensure n is treated as a 32-bit integer
|
||||
code[pos] = (n >> 24) & 0xff;
|
||||
code[pos + 1] = (n >> 16) & 0xff;
|
||||
code[pos + 2] = (n >> 8) & 0xff;
|
||||
code[pos + 3] = n & 0xff;
|
||||
}
|
||||
|
||||
function getWord(pos) {
|
||||
if (pos + WORD_SIZE > code.length) {
|
||||
throw new Error(`Get word out of bounds at position ${pos}`);
|
||||
}
|
||||
// Read bytes in big-endian order and combine into a 32-bit integer
|
||||
// Need to treat bytes as unsigned (0-255) using & 0xff
|
||||
let result = 0;
|
||||
result |= (code[pos] & 0xff) << 24;
|
||||
result |= (code[pos + 1] & 0xff) << 16;
|
||||
result |= (code[pos + 2] & 0xff) << 8;
|
||||
result |= (code[pos + 3] & 0xff);
|
||||
|
||||
// Handle sign bit if the result is expected to be signed
|
||||
// In JS, bitwise ops treat numbers as signed 32-bit.
|
||||
// The above combination *might* result in a negative number
|
||||
// if the most significant bit is set (for values >= 2^31).
|
||||
// If the VM expects unsigned, this is fine. If signed,
|
||||
// JS handles this conversion reasonably well after the bitwise ops.
|
||||
// Let's return the potentially signed result from the bitwise ops.
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function fetchVarOffset(name) {
|
||||
let n = variables.indexOf(name);
|
||||
if (n === -1) {
|
||||
variables.push(name);
|
||||
n = varCount++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
function fetchStringOffset(str) {
|
||||
let n = stringPool.indexOf(str);
|
||||
if (n === -1) {
|
||||
stringPool.push(str);
|
||||
n = stringCount++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
function hole() {
|
||||
const t = code.length;
|
||||
emitWord(0); // Emit a placeholder word (0)
|
||||
return t; // Return the position of the hole
|
||||
}
|
||||
|
||||
function arrayContains(arr, item) {
|
||||
return arr.includes(item); // JavaScript Array.prototype.includes is equivalent
|
||||
}
|
||||
|
||||
function codeGen(x) {
|
||||
let n, p1, p2;
|
||||
if (x === null) return;
|
||||
|
||||
switch (x.nt) {
|
||||
case NodeType.nd_None:
|
||||
return;
|
||||
case NodeType.nd_Ident:
|
||||
emitByte(Mnemonic.FETCH);
|
||||
n = fetchVarOffset(x.value);
|
||||
emitWord(n);
|
||||
break;
|
||||
case NodeType.nd_Integer:
|
||||
emitByte(Mnemonic.PUSH);
|
||||
emitWord(parseInt(x.value, 10)); // Use radix 10
|
||||
break;
|
||||
case NodeType.nd_String:
|
||||
emitByte(Mnemonic.PUSH);
|
||||
n = fetchStringOffset(x.value);
|
||||
emitWord(n);
|
||||
break;
|
||||
case NodeType.nd_Assign:
|
||||
n = fetchVarOffset(x.left.value);
|
||||
codeGen(x.right);
|
||||
emitByte(Mnemonic.STORE);
|
||||
emitWord(n);
|
||||
break;
|
||||
case NodeType.nd_If:
|
||||
// p2 needs scope beyond the if block, initialize before switch case
|
||||
codeGen(x.left); // Condition
|
||||
emitByte(Mnemonic.JZ); // Jump if condition is zero (false)
|
||||
p1 = hole(); // Placeholder for jump address (to 'else' or end of 'then')
|
||||
|
||||
codeGen(x.right.left); // 'then' block
|
||||
|
||||
if (x.right.right !== null) { // Check if 'else' block exists
|
||||
emitByte(Mnemonic.JMP); // Jump over 'else' block after 'then'
|
||||
p2 = hole(); // Placeholder for jump address (to end of 'else')
|
||||
}
|
||||
|
||||
// Patch the JZ instruction: jump relative from instruction *after* the word
|
||||
emitWordAt(p1, code.length - (p1 + WORD_SIZE));
|
||||
|
||||
if (x.right.right !== null) {
|
||||
codeGen(x.right.right); // 'else' block
|
||||
// Patch the JMP instruction: jump relative from instruction *after* the word
|
||||
emitWordAt(p2, code.length - (p2 + WORD_SIZE));
|
||||
}
|
||||
break;
|
||||
case NodeType.nd_While:
|
||||
p1 = code.length; // Start of the loop condition
|
||||
codeGen(x.left); // Condition
|
||||
emitByte(Mnemonic.JZ); // Jump if condition is zero (false)
|
||||
p2 = hole(); // Placeholder for jump address (to end of loop)
|
||||
|
||||
codeGen(x.right); // Loop body
|
||||
|
||||
emitByte(Mnemonic.JMP); // Jump back to the start of the loop condition
|
||||
// Jump relative from instruction *after* the word
|
||||
emitWord(p1 - (code.length + WORD_SIZE));
|
||||
|
||||
// Patch the JZ instruction: jump relative from instruction *after* the word
|
||||
emitWordAt(p2, code.length - (p2 + WORD_SIZE));
|
||||
break;
|
||||
case NodeType.nd_Sequence:
|
||||
codeGen(x.left);
|
||||
codeGen(x.right);
|
||||
break;
|
||||
case NodeType.nd_Prtc:
|
||||
codeGen(x.left);
|
||||
emitByte(Mnemonic.PRTC);
|
||||
break;
|
||||
case NodeType.nd_Prti:
|
||||
codeGen(x.left);
|
||||
emitByte(Mnemonic.PRTI);
|
||||
break;
|
||||
case NodeType.nd_Prts:
|
||||
codeGen(x.left);
|
||||
emitByte(Mnemonic.PRTS);
|
||||
break;
|
||||
default:
|
||||
if (arrayContains(operators, x.nt)) {
|
||||
codeGen(x.left);
|
||||
codeGen(x.right);
|
||||
emitByte(x.nt.mnemonic); // Use the mnemonic property
|
||||
} else if (arrayContains(unaryOps, x.nt)) {
|
||||
codeGen(x.left);
|
||||
emitByte(x.nt.mnemonic); // Use the mnemonic property
|
||||
} else {
|
||||
throw new Error(`Error in code generator! Found ${x.nt.name}, expecting operator.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function listCode() {
|
||||
let pc = 0;
|
||||
console.log(`Datasize: ${varCount} Strings: ${stringCount}`);
|
||||
for (const s of stringPool) {
|
||||
console.log(`"${s}"`); // Print strings, maybe quoted
|
||||
}
|
||||
|
||||
console.log("\n--- Code ---");
|
||||
|
||||
while (pc < code.length) {
|
||||
process.stdout.write(`${pc.toString().padStart(4, ' ')} `); // Format like printf %4d
|
||||
const opcodeValue = code[pc++];
|
||||
if (opcodeValue >= MnemonicByOrdinal.length) {
|
||||
throw new Error(`Unknown opcode value ${opcodeValue} @ ${pc - 1}`);
|
||||
}
|
||||
const op = MnemonicByOrdinal[opcodeValue];
|
||||
|
||||
let x;
|
||||
switch (op) {
|
||||
case Mnemonic.FETCH:
|
||||
x = getWord(pc);
|
||||
process.stdout.write(`fetch [${x}]`);
|
||||
pc += WORD_SIZE;
|
||||
break;
|
||||
case Mnemonic.STORE:
|
||||
x = getWord(pc);
|
||||
process.stdout.write(`store [${x}]`);
|
||||
pc += WORD_SIZE;
|
||||
break;
|
||||
case Mnemonic.PUSH:
|
||||
x = getWord(pc);
|
||||
process.stdout.write(`push ${x}`);
|
||||
pc += WORD_SIZE;
|
||||
break;
|
||||
case Mnemonic.ADD: case Mnemonic.SUB: case Mnemonic.MUL: case Mnemonic.DIV: case Mnemonic.MOD:
|
||||
case Mnemonic.LT: case Mnemonic.GT: case Mnemonic.LE: case Mnemonic.GE: case Mnemonic.EQ: case Mnemonic.NE:
|
||||
case Mnemonic.AND: case Mnemonic.OR: case Mnemonic.NEG: case Mnemonic.NOT:
|
||||
case Mnemonic.PRTC: case Mnemonic.PRTI: case Mnemonic.PRTS: case Mnemonic.HALT:
|
||||
process.stdout.write(op.name.toLowerCase());
|
||||
break;
|
||||
case Mnemonic.JMP:
|
||||
x = getWord(pc); // Read relative offset
|
||||
// Relative offset is calculated from the *instruction following the word* in the VM
|
||||
// pc points to the byte *after* the word now.
|
||||
process.stdout.write(`jmp (${x}) ${pc + x}`);
|
||||
pc += WORD_SIZE;
|
||||
break;
|
||||
case Mnemonic.JZ:
|
||||
x = getWord(pc); // Read relative offset
|
||||
// Relative offset is calculated from the *instruction following the word* in the VM
|
||||
// pc points to the byte *after* the word now.
|
||||
process.stdout.write(`jz (${x}) ${pc + x}`);
|
||||
pc += WORD_SIZE;
|
||||
break;
|
||||
default:
|
||||
// Should not happen if MnemonicByOrdinal is correctly built
|
||||
throw new Error(`Unknown opcode ${op.name} (${opcodeValue}) @ ${pc - 1}`);
|
||||
}
|
||||
console.log(); // Newline after each instruction
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Helper function to get the next line from the pre-loaded array
|
||||
function getNextLine() {
|
||||
if (currentLineIndex >= inputLines.length) {
|
||||
return null; // End of input
|
||||
}
|
||||
return inputLines[currentLineIndex++];
|
||||
}
|
||||
|
||||
function loadAst() {
|
||||
let command, value;
|
||||
let line = getNextLine();
|
||||
|
||||
while (line !== null) {
|
||||
value = null;
|
||||
// Check line length before substring to avoid errors
|
||||
if (line.length > 15) {
|
||||
command = line.substring(0, 15).trim();
|
||||
value = line.substring(15).trim();
|
||||
} else {
|
||||
command = line.trim();
|
||||
}
|
||||
|
||||
if (command === ";") {
|
||||
return null; // End of a sequence or node definition
|
||||
}
|
||||
|
||||
const nodeType = strToNodes.get(command);
|
||||
if (!nodeType) {
|
||||
throw new Error(`Command not found: '${command}' on line ${currentLineIndex}`);
|
||||
}
|
||||
|
||||
if (value !== null && value !== "") { // Check value is not just empty string
|
||||
// Note: The Java code checks value != null. If the AST format
|
||||
// allows "Identifier ", value will be "" after trim.
|
||||
// Let's stick closer to Java's null check, assuming non-leaf
|
||||
// nodes won't have anything after column 15 except whitespace.
|
||||
// Re-evaluating Java: `value != null` is set based on `line.length > 16`.
|
||||
// Let's emulate that logic closely.
|
||||
|
||||
// Java logic: value = line.substring(15).trim(); IF line.length > 16
|
||||
// Then `if (value != null)` returns leaf.
|
||||
// If line.length <= 16, command = line.trim(), value = null.
|
||||
// So leaf nodes MUST have something after col 15.
|
||||
// Let's adjust: if line.length > 15, value = line.substring(15).trim().
|
||||
// Then check if value is non-empty. This handles "Identifier " as non-leaf.
|
||||
|
||||
// Corrected logic:
|
||||
if (line.length > 15 && line.substring(15).trim() !== "") {
|
||||
value = line.substring(15).trim();
|
||||
return Node.makeLeaf(nodeType, value);
|
||||
}
|
||||
// If line.length <= 15 OR substring(15).trim() is empty,
|
||||
// it's not a leaf with a value in that part of the line.
|
||||
// Fall through to non-leaf handling.
|
||||
}
|
||||
|
||||
|
||||
// If it's not a leaf (or the leaf value part was empty/absent based on col 15)
|
||||
// Recursively load children
|
||||
const left = loadAst();
|
||||
const right = loadAst(); // Might be null for unary/sequence end
|
||||
|
||||
// Some nodes like Prtc, Prti, Prts, Negate, Not only have a left child
|
||||
// Based on the Java code_gen, these are treated as makeNode1 implicitly
|
||||
// but load_ast always tries to read two children.
|
||||
// The AST format must represent unary nodes with a ";" after the single child.
|
||||
// Example: Prtc\n Integer 10\n ;\n ;
|
||||
// This recursive loadAst handles that structure.
|
||||
return Node.makeNode(nodeType, left, right);
|
||||
}
|
||||
|
||||
// Should ideally not reach here if input is well-formed and ends with AST
|
||||
// But needed for compiler in Java, let's return null in JS too for safety.
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// --- Initialization ---
|
||||
function initialize() {
|
||||
strToNodes.set(";", NodeType.nd_None); // Special case for AST parsing termination
|
||||
strToNodes.set("Sequence", NodeType.nd_Sequence);
|
||||
strToNodes.set("Identifier", NodeType.nd_Ident);
|
||||
strToNodes.set("String", NodeType.nd_String);
|
||||
strToNodes.set("Integer", NodeType.nd_Integer);
|
||||
strToNodes.set("If", NodeType.nd_If);
|
||||
strToNodes.set("While", NodeType.nd_While);
|
||||
strToNodes.set("Prtc", NodeType.nd_Prtc);
|
||||
strToNodes.set("Prts", NodeType.nd_Prts);
|
||||
strToNodes.set("Prti", NodeType.nd_Prti);
|
||||
strToNodes.set("Assign", NodeType.nd_Assign);
|
||||
strToNodes.set("Negate", NodeType.nd_Negate);
|
||||
strToNodes.set("Not", NodeType.nd_Not);
|
||||
strToNodes.set("Multiply", NodeType.nd_Mul);
|
||||
strToNodes.set("Divide", NodeType.nd_Div);
|
||||
strToNodes.set("Mod", NodeType.nd_Mod);
|
||||
strToNodes.set("Add", NodeType.nd_Add);
|
||||
strToNodes.set("Subtract", NodeType.nd_Sub);
|
||||
strToNodes.set("Less", NodeType.nd_Lss);
|
||||
strToNodes.set("LessEqual", NodeType.nd_Leq);
|
||||
strToNodes.set("Greater", NodeType.nd_Gtr);
|
||||
strToNodes.set("GreaterEqual", NodeType.nd_Geq);
|
||||
strToNodes.set("Equal", NodeType.nd_Eql);
|
||||
strToNodes.set("NotEqual", NodeType.nd_Neq);
|
||||
strToNodes.set("And", NodeType.nd_And);
|
||||
strToNodes.set("Or", NodeType.nd_Or);
|
||||
}
|
||||
|
||||
|
||||
// --- Main Execution ---
|
||||
function main() {
|
||||
initialize();
|
||||
|
||||
const args = process.argv.slice(2); // Get command line arguments excluding 'node' and script name
|
||||
|
||||
if (args.length > 0) {
|
||||
const filename = args[0];
|
||||
try {
|
||||
// Read the entire file synchronously for simpler line-by-line processing
|
||||
const fileContent = fs.readFileSync(filename, 'utf8');
|
||||
inputLines = fileContent.split(/\r?\n/); // Split into lines, handling common line endings
|
||||
currentLineIndex = 0; // Reset line index for loadAst
|
||||
|
||||
const ast = loadAst();
|
||||
|
||||
if (ast) {
|
||||
codeGen(ast);
|
||||
emitByte(Mnemonic.HALT); // Emit HALT after generating code for the main AST
|
||||
listCode();
|
||||
} else {
|
||||
console.log("No valid AST loaded.");
|
||||
}
|
||||
|
||||
|
||||
} catch (e) {
|
||||
console.error(`Error: ${e.message}`);
|
||||
// console.error(e.stack); // Uncomment for detailed stack trace
|
||||
}
|
||||
} else {
|
||||
console.log("Usage: node code_generator.js <ast_file>");
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the main function
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
644
Task/Compiler-code-generator/Rust/compiler-code-generator.rs
Normal file
644
Task/Compiler-code-generator/Rust/compiler-code-generator.rs
Normal file
|
|
@ -0,0 +1,644 @@
|
|||
// src/main.rs
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead, BufReader, BufWriter, Write};
|
||||
use std::path::Path;
|
||||
use std::process;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
// --- Custom Error Type ---
|
||||
#[derive(Debug)]
|
||||
enum CompileError {
|
||||
Io(io::Error),
|
||||
Parse(String),
|
||||
CodeGen(String),
|
||||
Runtime(String), // For potential runtime errors if extended later
|
||||
}
|
||||
|
||||
impl fmt::Display for CompileError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
CompileError::Io(e) => write!(f, "IO Error: {}", e),
|
||||
CompileError::Parse(msg) => write!(f, "Parse Error: {}", msg),
|
||||
CompileError::CodeGen(msg) => write!(f, "Code Generation Error: {}", msg),
|
||||
CompileError::Runtime(msg) => write!(f, "Runtime Error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for CompileError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
CompileError::Io(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow converting io::Error into CompileError
|
||||
impl From<io::Error> for CompileError {
|
||||
fn from(err: io::Error) -> CompileError {
|
||||
CompileError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience type alias for Results
|
||||
type Result<T> = std::result::Result<T, CompileError>;
|
||||
|
||||
// --- Enums ---
|
||||
// Using u8 for Code as in the C code (uchar)
|
||||
type CodeByte = u8;
|
||||
|
||||
// Added derive attributes for convenience
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[repr(u8)] // Explicit representation matching C's implicit enum values
|
||||
enum NodeType {
|
||||
Ident, String, Integer, Sequence, If, Prtc, Prts, Prti, While,
|
||||
Assign, Negate, Not, Mul, Div, Mod, Add, Sub, Lss, Leq,
|
||||
Gtr, Geq, Eql, Neq, And, Or,
|
||||
// Added for parsing unknown types
|
||||
Unknown,
|
||||
}
|
||||
|
||||
// Mapping string names to NodeType - replacing get_enum_value C function
|
||||
fn node_type_from_str(s: &str) -> NodeType {
|
||||
match s {
|
||||
"Identifier" => NodeType::Ident,
|
||||
"String" => NodeType::String,
|
||||
"Integer" => NodeType::Integer,
|
||||
"Sequence" => NodeType::Sequence,
|
||||
"If" => NodeType::If,
|
||||
"Prtc" => NodeType::Prtc,
|
||||
"Prts" => NodeType::Prts,
|
||||
"Prti" => NodeType::Prti,
|
||||
"While" => NodeType::While,
|
||||
"Assign" => NodeType::Assign,
|
||||
"Negate" => NodeType::Negate,
|
||||
"Not" => NodeType::Not,
|
||||
"Multiply" => NodeType::Mul,
|
||||
"Divide" => NodeType::Div,
|
||||
"Mod" => NodeType::Mod,
|
||||
"Add" => NodeType::Add,
|
||||
"Subtract" => NodeType::Sub,
|
||||
"Less" => NodeType::Lss,
|
||||
"LessEqual" => NodeType::Leq,
|
||||
"Greater" => NodeType::Gtr,
|
||||
"GreaterEqual" => NodeType::Geq,
|
||||
"Equal" => NodeType::Eql,
|
||||
"NotEqual" => NodeType::Neq,
|
||||
"And" => NodeType::And,
|
||||
"Or" => NodeType::Or,
|
||||
_ => NodeType::Unknown, // Handle unknown tokens gracefully
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
enum OpCode {
|
||||
Fetch, Store, Push, Add, Sub, Mul, Div, Mod, Lt, Gt, Le, Ge, Eq, Ne, And,
|
||||
Or, Neg, Not, Jmp, Jz, Prtc, Prts, Prti, Halt,
|
||||
}
|
||||
|
||||
// --- Structs ---
|
||||
#[derive(Debug, Clone)] // Added Clone for easier handling if needed
|
||||
struct Tree {
|
||||
node_type: NodeType,
|
||||
// Use Option<Box<Tree>> for optional children
|
||||
left: Option<Box<Tree>>,
|
||||
right: Option<Box<Tree>>,
|
||||
// Use String for owned string values
|
||||
value: Option<String>,
|
||||
}
|
||||
|
||||
impl Tree {
|
||||
// Constructor for nodes with children
|
||||
fn new_node(node_type: NodeType, left: Tree, right: Tree) -> Box<Tree> {
|
||||
Box::new(Tree {
|
||||
node_type,
|
||||
left: Some(Box::new(left)),
|
||||
right: Some(Box::new(right)),
|
||||
value: None,
|
||||
})
|
||||
}
|
||||
|
||||
// Constructor for nodes with children (accepting Option<Box<Tree>>)
|
||||
fn new_node_option(node_type: NodeType, left: Option<Box<Tree>>, right: Option<Box<Tree>>) -> Box<Tree> {
|
||||
Box::new(Tree {
|
||||
node_type,
|
||||
left,
|
||||
right,
|
||||
value: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Constructor for leaf nodes with a value
|
||||
fn new_leaf(node_type: NodeType, value: &str) -> Box<Tree> {
|
||||
Box::new(Tree {
|
||||
node_type,
|
||||
left: None,
|
||||
right: None,
|
||||
value: Some(value.to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// --- Code Generation State ---
|
||||
struct CodeGenState {
|
||||
object: Vec<CodeByte>, // Replaces da_dim(object, code)
|
||||
globals: Vec<String>, // Replaces da_dim(globals, const char *)
|
||||
string_pool: Vec<String>, // Replaces da_dim(string_pool, const char *)
|
||||
// `here` is implicitly `object.len()`
|
||||
}
|
||||
|
||||
impl CodeGenState {
|
||||
fn new() -> Self {
|
||||
CodeGenState {
|
||||
object: Vec::new(),
|
||||
globals: Vec::new(),
|
||||
string_pool: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Get current code position
|
||||
fn here(&self) -> usize {
|
||||
self.object.len()
|
||||
}
|
||||
|
||||
// Replaces emit_byte
|
||||
fn emit_byte(&mut self, byte: CodeByte) {
|
||||
self.object.push(byte);
|
||||
}
|
||||
|
||||
// Emit an opcode
|
||||
fn emit_opcode(&mut self, opcode: OpCode) {
|
||||
self.object.push(opcode as CodeByte);
|
||||
}
|
||||
|
||||
// Replaces emit_int - uses little-endian encoding
|
||||
fn emit_int(&mut self, value: i32) {
|
||||
self.object.extend_from_slice(&value.to_le_bytes());
|
||||
}
|
||||
|
||||
// Replaces hole()
|
||||
fn emit_hole(&mut self) -> usize {
|
||||
let pos = self.here();
|
||||
self.emit_int(0); // Placeholder value
|
||||
pos
|
||||
}
|
||||
|
||||
// Replaces fix() - patches a previous hole
|
||||
// Uses little-endian encoding
|
||||
fn fix_jump(&mut self, hole_pos: usize, jump_target: usize) -> Result<()> {
|
||||
if hole_pos + 4 > self.object.len() {
|
||||
return Err(CompileError::CodeGen("Invalid hole position for jump fix".to_string()));
|
||||
}
|
||||
// Calculate relative offset
|
||||
let offset = jump_target as isize - (hole_pos as isize + 4); // offset is from *after* the int
|
||||
let offset_bytes = (offset as i32).to_le_bytes();
|
||||
self.object[hole_pos..hole_pos + 4].copy_from_slice(&offset_bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Like C's fix, but uses absolute target address for patching data later
|
||||
// Use for patching arbitrary data, not jumps
|
||||
fn fix_data(&mut self, pos: usize, value: i32) -> Result<()> {
|
||||
if pos + 4 > self.object.len() {
|
||||
return Err(CompileError::CodeGen("Invalid position for data fix".to_string()));
|
||||
}
|
||||
let value_bytes = value.to_le_bytes();
|
||||
self.object[pos..pos + 4].copy_from_slice(&value_bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
// Replaces fetch_var_offset
|
||||
fn fetch_var_offset(&mut self, id: &str) -> usize {
|
||||
if let Some(pos) = self.globals.iter().position(|g| g == id) {
|
||||
pos
|
||||
} else {
|
||||
self.globals.push(id.to_string());
|
||||
self.globals.len() - 1
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces fetch_string_offset
|
||||
fn fetch_string_offset(&mut self, st: &str) -> usize {
|
||||
if let Some(pos) = self.string_pool.iter().position(|s| s == st) {
|
||||
pos
|
||||
} else {
|
||||
self.string_pool.push(st.to_string());
|
||||
self.string_pool.len() - 1
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces type_to_op
|
||||
fn type_to_op(node_type: NodeType) -> Option<OpCode> {
|
||||
match node_type {
|
||||
NodeType::Negate => Some(OpCode::Neg),
|
||||
NodeType::Not => Some(OpCode::Not),
|
||||
NodeType::Mul => Some(OpCode::Mul),
|
||||
NodeType::Div => Some(OpCode::Div),
|
||||
NodeType::Mod => Some(OpCode::Mod),
|
||||
NodeType::Add => Some(OpCode::Add),
|
||||
NodeType::Sub => Some(OpCode::Sub),
|
||||
NodeType::Lss => Some(OpCode::Lt),
|
||||
NodeType::Leq => Some(OpCode::Le),
|
||||
NodeType::Gtr => Some(OpCode::Gt),
|
||||
NodeType::Geq => Some(OpCode::Ge),
|
||||
NodeType::Eql => Some(OpCode::Eq),
|
||||
NodeType::Neq => Some(OpCode::Ne),
|
||||
NodeType::And => Some(OpCode::And),
|
||||
NodeType::Or => Some(OpCode::Or),
|
||||
_ => None, // Other node types don't map directly to a single opcode
|
||||
}
|
||||
}
|
||||
|
||||
// --- Code Generator Logic ---
|
||||
// Takes Option<&Tree> because some nodes might have absent children
|
||||
fn code_gen(&mut self, node: Option<&Tree>) -> Result<()> {
|
||||
let node = match node {
|
||||
Some(n) => n,
|
||||
None => return Ok(()), // Nothing to generate for a None node
|
||||
};
|
||||
|
||||
match node.node_type {
|
||||
NodeType::Ident => {
|
||||
let ident = node.value.as_ref().ok_or_else(|| {
|
||||
CompileError::CodeGen("Identifier node is missing value".to_string())
|
||||
})?;
|
||||
self.emit_opcode(OpCode::Fetch);
|
||||
let offset = self.fetch_var_offset(ident);
|
||||
self.emit_int(offset as i32);
|
||||
}
|
||||
NodeType::Integer => {
|
||||
let val_str = node.value.as_ref().ok_or_else(|| {
|
||||
CompileError::CodeGen("Integer node is missing value".to_string())
|
||||
})?;
|
||||
let value = val_str.parse::<i32>().map_err(|e| {
|
||||
CompileError::Parse(format!("Invalid integer literal '{}': {}", val_str, e))
|
||||
})?;
|
||||
self.emit_opcode(OpCode::Push);
|
||||
self.emit_int(value);
|
||||
}
|
||||
NodeType::String => {
|
||||
let val_str = node.value.as_ref().ok_or_else(|| {
|
||||
CompileError::CodeGen("String node is missing value".to_string())
|
||||
})?;
|
||||
self.emit_opcode(OpCode::Push);
|
||||
let offset = self.fetch_string_offset(val_str);
|
||||
self.emit_int(offset as i32);
|
||||
}
|
||||
NodeType::Assign => {
|
||||
let ident_node = node.left.as_ref().ok_or_else(|| {
|
||||
CompileError::CodeGen("Assignment missing identifier (left side)".to_string())
|
||||
})?;
|
||||
let ident = ident_node.value.as_ref().ok_or_else(|| {
|
||||
CompileError::CodeGen("Assignment identifier node missing value".to_string())
|
||||
})?;
|
||||
if ident_node.node_type != NodeType::Ident {
|
||||
return Err(CompileError::CodeGen("Left side of assignment must be an identifier".to_string()));
|
||||
}
|
||||
|
||||
let var_offset = self.fetch_var_offset(ident);
|
||||
self.code_gen(node.right.as_deref())?; // Generate value first
|
||||
self.emit_opcode(OpCode::Store);
|
||||
self.emit_int(var_offset as i32);
|
||||
}
|
||||
NodeType::If => {
|
||||
// If condition
|
||||
self.code_gen(node.left.as_deref())?;
|
||||
self.emit_opcode(OpCode::Jz); // Jump if zero (false)
|
||||
let jump_over_true_branch_hole = self.emit_hole();
|
||||
|
||||
// True branch (must be present, wrapped in Sequence or single node)
|
||||
let if_body = node.right.as_ref().ok_or_else(|| CompileError::CodeGen("If node missing body (right side)".to_string()))?;
|
||||
|
||||
self.code_gen(if_body.left.as_deref())?; // Execute 'then' part
|
||||
|
||||
// Handle optional 'else' part
|
||||
let mut jump_over_else_branch_hole: Option<usize> = None;
|
||||
if if_body.right.is_some() {
|
||||
self.emit_opcode(OpCode::Jmp); // Jump over the 'else' part
|
||||
jump_over_else_branch_hole = Some(self.emit_hole());
|
||||
}
|
||||
|
||||
// Patch the first jump (jz) to point after the 'then' block (or after the 'jmp' if there's an else)
|
||||
let after_true_branch = self.here();
|
||||
self.fix_jump(jump_over_true_branch_hole, after_true_branch)?;
|
||||
|
||||
// Generate 'else' block if it exists
|
||||
if let Some(else_node) = &if_body.right {
|
||||
self.code_gen(Some(else_node))?;
|
||||
// Patch the jump over the else branch
|
||||
let after_else_branch = self.here();
|
||||
if let Some(hole) = jump_over_else_branch_hole {
|
||||
self.fix_jump(hole, after_else_branch)?;
|
||||
} else {
|
||||
// Should not happen if if_body.right was Some
|
||||
return Err(CompileError::CodeGen("Internal error: Missing else jump hole".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
NodeType::While => {
|
||||
let condition_start = self.here();
|
||||
self.code_gen(node.left.as_deref())?; // While expr
|
||||
self.emit_opcode(OpCode::Jz); // If false, jump to end
|
||||
let exit_jump_hole = self.emit_hole();
|
||||
|
||||
self.code_gen(node.right.as_deref())?; // Loop body
|
||||
self.emit_opcode(OpCode::Jmp); // Jump back to condition
|
||||
// Use emit_hole + fix_jump for the backward jump
|
||||
let back_jump_hole = self.emit_hole();
|
||||
self.fix_jump(back_jump_hole, condition_start)?;
|
||||
|
||||
let after_loop = self.here();
|
||||
self.fix_jump(exit_jump_hole, after_loop)?; // Patch the exit jump
|
||||
}
|
||||
NodeType::Sequence => {
|
||||
self.code_gen(node.left.as_deref())?;
|
||||
self.code_gen(node.right.as_deref())?;
|
||||
}
|
||||
NodeType::Prtc => {
|
||||
self.code_gen(node.left.as_deref())?;
|
||||
self.emit_opcode(OpCode::Prtc);
|
||||
}
|
||||
NodeType::Prti => {
|
||||
self.code_gen(node.left.as_deref())?;
|
||||
self.emit_opcode(OpCode::Prti);
|
||||
}
|
||||
NodeType::Prts => {
|
||||
self.code_gen(node.left.as_deref())?;
|
||||
self.emit_opcode(OpCode::Prts);
|
||||
}
|
||||
// Binary Operators
|
||||
NodeType::Lss | NodeType::Gtr | NodeType::Leq | NodeType::Geq |
|
||||
NodeType::Eql | NodeType::Neq | NodeType::And | NodeType::Or |
|
||||
NodeType::Sub | NodeType::Add | NodeType::Div | NodeType::Mul |
|
||||
NodeType::Mod => {
|
||||
self.code_gen(node.left.as_deref())?;
|
||||
self.code_gen(node.right.as_deref())?;
|
||||
if let Some(op) = CodeGenState::type_to_op(node.node_type) {
|
||||
self.emit_opcode(op);
|
||||
} else {
|
||||
return Err(CompileError::CodeGen(format!(
|
||||
"Cannot map node type {:?} to binary opcode", node.node_type
|
||||
)));
|
||||
}
|
||||
}
|
||||
// Unary Operators
|
||||
NodeType::Negate | NodeType::Not => {
|
||||
self.code_gen(node.left.as_deref())?;
|
||||
if let Some(op) = CodeGenState::type_to_op(node.node_type) {
|
||||
self.emit_opcode(op);
|
||||
} else {
|
||||
return Err(CompileError::CodeGen(format!(
|
||||
"Cannot map node type {:?} to unary opcode", node.node_type
|
||||
)));
|
||||
}
|
||||
}
|
||||
NodeType::Unknown => {
|
||||
return Err(CompileError::CodeGen("Encountered Unknown node type during code generation".to_string()));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Replaces code_finish
|
||||
fn code_finish(&mut self) {
|
||||
self.emit_opcode(OpCode::Halt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Disassembler ---
|
||||
// Replaces list_code
|
||||
// Takes BufWriter for potentially better performance with many writes
|
||||
fn list_code<W: Write>(state: &CodeGenState, writer: &mut BufWriter<W>) -> Result<()> {
|
||||
writeln!(writer, "Datasize: {} Strings: {}", state.globals.len(), state.string_pool.len())?;
|
||||
for s in &state.string_pool {
|
||||
writeln!(writer, "{}", s)?;
|
||||
}
|
||||
writeln!(writer, "--- Code ---")?;
|
||||
|
||||
let code = &state.object;
|
||||
let mut pc = 0;
|
||||
|
||||
while pc < code.len() {
|
||||
let start_pc = pc;
|
||||
write!(writer, "{:5} ", start_pc)?;
|
||||
|
||||
// Function to safely read an i32 operand
|
||||
let read_operand = |current_pc: usize| -> Result<(i32, usize)> {
|
||||
let end_operand = current_pc + 4;
|
||||
if end_operand > code.len() {
|
||||
Err(CompileError::Runtime("Unexpected end of code while reading operand".to_string()))
|
||||
} else {
|
||||
// Read bytes assuming little-endian
|
||||
let mut bytes = [0u8; 4];
|
||||
bytes.copy_from_slice(&code[current_pc..end_operand]);
|
||||
Ok((i32::from_le_bytes(bytes), end_operand))
|
||||
}
|
||||
};
|
||||
|
||||
// Function to safely read an opcode byte
|
||||
let read_opcode_byte = |current_pc: usize| -> Result<(u8, usize)> {
|
||||
if current_pc >= code.len() {
|
||||
Err(CompileError::Runtime("Unexpected end of code while reading opcode".to_string()))
|
||||
} else {
|
||||
Ok((code[current_pc], current_pc + 1))
|
||||
}
|
||||
};
|
||||
|
||||
let (opcode_byte, next_pc) = read_opcode_byte(pc)?;
|
||||
pc = next_pc; // Move pc past the opcode byte
|
||||
|
||||
// Try to convert byte to OpCode enum
|
||||
let opcode: OpCode = unsafe {
|
||||
// This is safe IF OpCode is repr(u8) and opcode_byte is a valid variant value.
|
||||
// A safer way would be a match statement or a function.
|
||||
if opcode_byte <= OpCode::Halt as u8 {
|
||||
std::mem::transmute(opcode_byte)
|
||||
} else {
|
||||
writeln!(writer, "ERROR: Unknown opcode byte {}", opcode_byte)?;
|
||||
return Err(CompileError::Runtime(format!("Unknown opcode byte: {}", opcode_byte)));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
match opcode {
|
||||
OpCode::Fetch => {
|
||||
let (operand, next_pc) = read_operand(pc)?; pc = next_pc;
|
||||
writeln!(writer, "fetch [{}]", operand)?;
|
||||
}
|
||||
OpCode::Store => {
|
||||
let (operand, next_pc) = read_operand(pc)?; pc = next_pc;
|
||||
writeln!(writer, "store [{}]", operand)?;
|
||||
}
|
||||
OpCode::Push => {
|
||||
let (operand, next_pc) = read_operand(pc)?; pc = next_pc;
|
||||
writeln!(writer, "push {}", operand)?;
|
||||
}
|
||||
OpCode::Add => writeln!(writer, "add")?,
|
||||
OpCode::Sub => writeln!(writer, "sub")?,
|
||||
OpCode::Mul => writeln!(writer, "mul")?,
|
||||
OpCode::Div => writeln!(writer, "div")?,
|
||||
OpCode::Mod => writeln!(writer, "mod")?,
|
||||
OpCode::Lt => writeln!(writer, "lt")?,
|
||||
OpCode::Gt => writeln!(writer, "gt")?,
|
||||
OpCode::Le => writeln!(writer, "le")?,
|
||||
OpCode::Ge => writeln!(writer, "ge")?,
|
||||
OpCode::Eq => writeln!(writer, "eq")?,
|
||||
OpCode::Ne => writeln!(writer, "ne")?,
|
||||
OpCode::And => writeln!(writer, "and")?,
|
||||
OpCode::Or => writeln!(writer, "or")?,
|
||||
OpCode::Neg => writeln!(writer, "neg")?,
|
||||
OpCode::Not => writeln!(writer, "not")?,
|
||||
OpCode::Jmp | OpCode::Jz => {
|
||||
let (offset, next_pc) = read_operand(pc)?; pc = next_pc;
|
||||
// Calculate absolute target address
|
||||
// offset is relative to the instruction *after* the operand
|
||||
let target_pc = (pc as isize + offset as isize) as usize;
|
||||
let op_str = if opcode == OpCode::Jmp { "jmp" } else { "jz " };
|
||||
writeln!(writer, "{} ({}) {}", op_str, offset, target_pc)?;
|
||||
|
||||
}
|
||||
OpCode::Prtc => writeln!(writer, "prtc")?,
|
||||
OpCode::Prti => writeln!(writer, "prti")?,
|
||||
OpCode::Prts => writeln!(writer, "prts")?,
|
||||
OpCode::Halt => {
|
||||
writeln!(writer, "halt")?;
|
||||
break; // Stop disassembly after HALT
|
||||
}
|
||||
// The transmute above should prevent reaching here if OpCode is exhaustive
|
||||
// _ => {
|
||||
// writeln!(writer, "ERROR: Unknown opcode {}", opcode_byte)?;
|
||||
// return Err(CompileError::Runtime(format!("Unknown opcode byte: {}", opcode_byte)));
|
||||
// }
|
||||
}
|
||||
}
|
||||
writer.flush()?; // Ensure buffer is written
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
// --- AST Loader ---
|
||||
struct AstLoader<R: BufRead> {
|
||||
reader: R,
|
||||
line_buffer: String,
|
||||
}
|
||||
|
||||
impl<R: BufRead> AstLoader<R> {
|
||||
fn new(reader: R) -> Self {
|
||||
AstLoader {
|
||||
reader,
|
||||
line_buffer: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces read_line and rtrim
|
||||
fn read_next_line(&mut self) -> Result<Option<&str>> {
|
||||
self.line_buffer.clear();
|
||||
match self.reader.read_line(&mut self.line_buffer)? {
|
||||
0 => Ok(None), // EOF
|
||||
_ => Ok(Some(self.line_buffer.trim_end())), // Return trimmed line
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces load_ast (recursive part)
|
||||
fn load_ast_recursive(&mut self) -> Result<Option<Box<Tree>>> {
|
||||
let line = match self.read_next_line()? {
|
||||
Some(l) if !l.is_empty() && !l.starts_with(';') => l,
|
||||
Some(_) => return self.load_ast_recursive(), // Skip empty lines or comments
|
||||
None => return Ok(None), // End of input
|
||||
};
|
||||
|
||||
let mut parts = line.splitn(2, ' '); // Split into token and the rest
|
||||
let token = parts.next().ok_or_else(|| CompileError::Parse("Empty line encountered unexpectedly".to_string()))?;
|
||||
let maybe_value = parts.next();
|
||||
|
||||
let node_type = node_type_from_str(token);
|
||||
if node_type == NodeType::Unknown {
|
||||
return Err(CompileError::Parse(format!("Unknown token '{}'", token)));
|
||||
}
|
||||
|
||||
// Check if it's a leaf node (has extra data on the same line)
|
||||
if let Some(value_part) = maybe_value {
|
||||
let trimmed_value = value_part.trim_start();
|
||||
if !trimmed_value.is_empty() {
|
||||
// It's a leaf node (Identifier, String, Integer)
|
||||
match node_type {
|
||||
NodeType::Ident | NodeType::String | NodeType::Integer => {
|
||||
Ok(Some(Tree::new_leaf(node_type, trimmed_value)))
|
||||
}
|
||||
_ => Err(CompileError::Parse(format!(
|
||||
"Node type {:?} unexpectedly found with value '{}' on the same line",
|
||||
node_type, trimmed_value
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
// It's an internal node, load children recursively
|
||||
let left = self.load_ast_recursive()?;
|
||||
let right = self.load_ast_recursive()?;
|
||||
Ok(Some(Tree::new_node_option(node_type, left, right)))
|
||||
}
|
||||
} else {
|
||||
// No extra data, must be an internal node with children on subsequent lines
|
||||
let left = self.load_ast_recursive()?;
|
||||
let right = self.load_ast_recursive()?;
|
||||
Ok(Some(Tree::new_node_option(node_type, left, right)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Main Function ---
|
||||
fn main() {
|
||||
if let Err(e) = run() {
|
||||
eprintln!("Error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> Result<()> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let source_filename = args.get(1).map(String::as_str).unwrap_or("");
|
||||
let dest_filename = args.get(2).map(String::as_str).unwrap_or("");
|
||||
|
||||
// --- Setup I/O ---
|
||||
// Use Box<dyn BufRead> to handle both Stdin and Files
|
||||
let source_reader: Box<dyn BufRead> = if source_filename.is_empty() {
|
||||
Box::new(BufReader::new(io::stdin()))
|
||||
} else {
|
||||
let file = File::open(Path::new(source_filename))
|
||||
.map_err(|e| CompileError::Io(e))?; // Convert IO error
|
||||
Box::new(BufReader::new(file))
|
||||
};
|
||||
|
||||
// Use Box<dyn Write> and BufWriter for output
|
||||
let dest_writer: Box<dyn Write> = if dest_filename.is_empty() {
|
||||
Box::new(io::stdout())
|
||||
} else {
|
||||
let file = File::create(Path::new(dest_filename))
|
||||
.map_err(|e| CompileError::Io(e))?;
|
||||
Box::new(file)
|
||||
};
|
||||
let mut buffered_writer = BufWriter::new(dest_writer);
|
||||
|
||||
|
||||
// --- Load AST ---
|
||||
let mut loader = AstLoader::new(source_reader);
|
||||
let ast_root = loader.load_ast_recursive()?;
|
||||
|
||||
// --- Code Generation ---
|
||||
let mut state = CodeGenState::new();
|
||||
state.code_gen(ast_root.as_deref())?; // Pass reference
|
||||
state.code_finish();
|
||||
|
||||
// --- List Code ---
|
||||
list_code(&state, &mut buffered_writer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue