Data update

This commit is contained in:
Ingy döt Net 2025-06-11 20:16:52 -04:00
parent 72eb4943cb
commit 4d5544505c
2347 changed files with 62432 additions and 16731 deletions

View file

@ -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();
}

View 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(())
}