mega refactor:
essentially completely reorganised the project. previous contents of the dsa folder have now been split into emulator/ tooling/ and libraries/ libraries is for common functionality across crates, tooling is for core binaries needed for building and debugging DSA projects, including dsx client/server/build emulator contains the dsa_emulator crate that implements the core DSA emulator, the dsa_gui crate that implements the graphical user interface for the emulator & all future tooling under a unified application, and finally the dsa_cli crate which will eventually provide lightweight wrapper for the core emulator, simply providing a tool for running DSA binaries without the overhead of a heavy GUI.
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "assembler"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "dsa-a"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "assembler"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.6.0", features = ["derive"] }
|
||||
common = { path = "../../libraries/common" }
|
||||
dsa_formats = { path = "../../libraries/dsa_formats" }
|
||||
|
||||
num_cpus = "1.17.0"
|
||||
strum = { version = "0.28.0", features = ["derive"] }
|
||||
threadpool = "1.8.1"
|
||||
@@ -0,0 +1,395 @@
|
||||
use common::prelude::*;
|
||||
use common::process::ProcessAgent;
|
||||
use crate::assembler::Token;
|
||||
use crate::assembler::model::{Node, Opcode};
|
||||
use crate::{assembler::AssembleError, expect_token};
|
||||
|
||||
pub fn codegen(nodes: Vec<Node>, ctx: &ProcessAgent) -> Result<Vec<Instruction>, AssembleError> {
|
||||
let mut instructions = vec![];
|
||||
|
||||
for node in nodes {
|
||||
ctx.log(format!("{}", node));
|
||||
instructions.push(build_instruction(&node)?);
|
||||
}
|
||||
|
||||
ctx.log("Assembly Successful ✅");
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
fn build_instruction(node: &Node) -> Result<Instruction, AssembleError> {
|
||||
let opcode = node.opcode();
|
||||
let args = node.args();
|
||||
|
||||
match opcode {
|
||||
Opcode::Nop => Ok(Instruction::nop()),
|
||||
Opcode::Mov => ins_mov(opcode, &args),
|
||||
Opcode::CMov => ins_cmov(opcode, &args),
|
||||
Opcode::Ldb
|
||||
| Opcode::Ldw
|
||||
| Opcode::Ldh
|
||||
| Opcode::Ldbs
|
||||
| Opcode::Ldhs
|
||||
| Opcode::Stb
|
||||
| Opcode::Stw
|
||||
| Opcode::Sth => ins_ldx_stx(opcode, &args),
|
||||
Opcode::Lli | Opcode::Lui => ins_load_imm(opcode, &args),
|
||||
Opcode::Push | Opcode::Pop => ins_stack(opcode, &args),
|
||||
Opcode::Ieq | Opcode::Ine | Opcode::Igt | Opcode::Ige | Opcode::Ile | Opcode::Ilt => {
|
||||
ins_comparison(opcode, &args)
|
||||
}
|
||||
Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc => {
|
||||
ins_jump_unconditional(opcode, &args)
|
||||
}
|
||||
Opcode::Jez | Opcode::Jnz => ins_jump_conditional(opcode, &args),
|
||||
Opcode::Shl | Opcode::Shr => ins_bitshift(opcode, &args),
|
||||
Opcode::Add
|
||||
| Opcode::Sub
|
||||
| Opcode::And
|
||||
| Opcode::Or
|
||||
| Opcode::Xor
|
||||
| Opcode::Nand
|
||||
| Opcode::Nor
|
||||
| Opcode::Xnor => ins_arithmetic(opcode, &args),
|
||||
Opcode::AddI | Opcode::SubI => ins_imm_arithmetic(opcode, &args),
|
||||
Opcode::Not => ins_not(&args),
|
||||
Opcode::Int => ins_interrupt(&args),
|
||||
Opcode::Ret => Ok(Instruction::ret()),
|
||||
Opcode::IRet => Ok(Instruction::irt()),
|
||||
Opcode::Hlt => Ok(Instruction::hlt()),
|
||||
Opcode::Data => build_data_instruction(&args),
|
||||
Opcode::Segment => build_segment_instruction(&args),
|
||||
Opcode::Db
|
||||
| Opcode::Dh
|
||||
| Opcode::Dw
|
||||
| Opcode::Resb
|
||||
| Opcode::Resh
|
||||
| Opcode::Resw
|
||||
| Opcode::Lwi
|
||||
| Opcode::Include
|
||||
| Opcode::Func
|
||||
| Opcode::Return => Err(AssembleError::InvalidArg),
|
||||
}
|
||||
}
|
||||
|
||||
fn ins_mov(
|
||||
opcode: Opcode,
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(src_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
let Some(dest_token) = args.get(1) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
|
||||
let src = expect_token!(src_token, Register)?;
|
||||
let dest = expect_token!(dest_token, Register)?;
|
||||
|
||||
match opcode {
|
||||
Opcode::Mov => Ok(Instruction::mov(src, dest)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn ins_cmov(
|
||||
opcode: Opcode,
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(src_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
let Some(dest_token) = args.get(1) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
let Some(cmp_token) = args.get(2) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
|
||||
let src = expect_token!(src_token, Register)?;
|
||||
let dest = expect_token!(dest_token, Register)?;
|
||||
let cmp = expect_token!(cmp_token, Register)?;
|
||||
|
||||
match opcode {
|
||||
Opcode::CMov => Ok(Instruction::cmov(src, dest, cmp)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn ins_ldx_stx(
|
||||
opcode: Opcode,
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(src_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
let Some(dest_token) = args.get(1) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
let Some(offset_token) = args.get(2) else {
|
||||
return Err(AssembleError::MissingArgument(2));
|
||||
};
|
||||
|
||||
let src = expect_token!(src_token, Register)?;
|
||||
let dest = expect_token!(dest_token, Register)?;
|
||||
let offset = expect_token!(offset_token, Immediate)?;
|
||||
|
||||
match opcode {
|
||||
Opcode::Ldb => Ok(Instruction::ldb(src, dest, offset as u16)),
|
||||
Opcode::Ldbs => Ok(Instruction::ldbs(src, dest, offset as u16)),
|
||||
Opcode::Ldh => Ok(Instruction::ldh(src, dest, offset as u16)),
|
||||
Opcode::Ldhs => Ok(Instruction::ldhs(src, dest, offset as u16)),
|
||||
Opcode::Ldw => Ok(Instruction::ldw(src, dest, offset as u16)),
|
||||
Opcode::Stb => Ok(Instruction::stb(src, dest, offset as u16)),
|
||||
Opcode::Sth => Ok(Instruction::sth(src, dest, offset as u16)),
|
||||
Opcode::Stw => Ok(Instruction::stw(src, dest, offset as u16)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn ins_stack(
|
||||
opcode: Opcode,
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(reg_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
|
||||
let reg = expect_token!(reg_token, Register)?;
|
||||
match opcode {
|
||||
Opcode::Push => Ok(Instruction::push(reg)),
|
||||
Opcode::Pop => Ok(Instruction::pop(reg)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn ins_load_imm(
|
||||
opcode: Opcode,
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(value_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
let Some(dest_token) = args.get(1) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
|
||||
let value = expect_token!(value_token, Immediate)?;
|
||||
let dest = expect_token!(dest_token, Register)?;
|
||||
|
||||
match opcode {
|
||||
Opcode::Lli => Ok(Instruction::lli(dest, value as u16)),
|
||||
Opcode::Lui => Ok(Instruction::lui(dest, (value >> 16) as u16)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn ins_jump_unconditional(
|
||||
opcode: Opcode,
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(addr_imm_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
// addr_reg is optional; default to Register::Zero if missing
|
||||
let addr_reg_token = args.get(1).unwrap_or(&Token::Register(Register::Zero));
|
||||
|
||||
let addr_imm = expect_token!(addr_imm_token, Immediate)?;
|
||||
let addr_reg = expect_token!(addr_reg_token, Register)?;
|
||||
|
||||
match opcode {
|
||||
Opcode::Jmp => Ok(Instruction::jmp(addr_reg, addr_imm as u16)),
|
||||
Opcode::Jic => Ok(Instruction::jic(addr_reg, addr_imm as u16)),
|
||||
Opcode::Jnc => Ok(Instruction::jnc(addr_reg, addr_imm as u16)),
|
||||
Opcode::Call => Ok(Instruction::call(addr_reg, addr_imm as u16)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn ins_jump_conditional(
|
||||
opcode: Opcode,
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(condition_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
let Some(addr_imm_token) = args.get(1) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
let addr_reg_token = if let Some(token) = args.get(2) {
|
||||
token
|
||||
} else {
|
||||
&Token::Register(Register::Zero)
|
||||
};
|
||||
|
||||
let condition = expect_token!(condition_token, Register)?;
|
||||
let addr_imm = expect_token!(addr_imm_token, Immediate)?;
|
||||
let addr_reg = expect_token!(addr_reg_token, Register)?;
|
||||
|
||||
Ok(match opcode {
|
||||
Opcode::Jez => Instruction::jez(condition, addr_reg, addr_imm as u16),
|
||||
Opcode::Jnz => Instruction::jnz(condition, addr_reg, addr_imm as u16),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
fn ins_comparison(
|
||||
opcode: Opcode,
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(left_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
let Some(right_token) = args.get(1) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
let Some(dest_token) = args.get(2) else {
|
||||
return Err(AssembleError::MissingArgument(2));
|
||||
};
|
||||
|
||||
let left = expect_token!(left_token, Register)?;
|
||||
let right = expect_token!(right_token, Register)?;
|
||||
let dest = expect_token!(dest_token, Register)?;
|
||||
Ok(match opcode {
|
||||
Opcode::Ieq => Instruction::ieq(left, right, dest),
|
||||
Opcode::Ine => Instruction::ine(left, right, dest),
|
||||
Opcode::Igt => Instruction::igt(left, right, dest),
|
||||
Opcode::Ige => Instruction::ige(left, right, dest),
|
||||
Opcode::Ile => Instruction::ile(left, right, dest),
|
||||
Opcode::Ilt => Instruction::ilt(left, right, dest),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
fn ins_bitshift(
|
||||
opcode: Opcode,
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(src_reg) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
let Some(r_shamt) = args.get(1) else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
let Some(i_shamt) = args.get(2) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
let Some(dest_reg) = args.get(3) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
|
||||
let src = expect_token!(src_reg, Register)?;
|
||||
let r_shamt = expect_token!(r_shamt, Register)?;
|
||||
let i_shamt = expect_token!(i_shamt, Immediate)? as u8;
|
||||
let dest = expect_token!(dest_reg, Register)?;
|
||||
|
||||
Ok(match opcode {
|
||||
Opcode::Shl => Instruction::shl(src, r_shamt, dest, i_shamt),
|
||||
Opcode::Shr => Instruction::shr(src, r_shamt, dest, i_shamt),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
fn ins_arithmetic(
|
||||
opcode: Opcode,
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(left_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
let Some(right_token) = args.get(1) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
let Some(dest_token) = args.get(2) else {
|
||||
return Err(AssembleError::MissingArgument(2));
|
||||
};
|
||||
|
||||
let left = expect_token!(left_token, Register)?;
|
||||
let right = expect_token!(right_token, Register)?;
|
||||
let dest = expect_token!(dest_token, Register)?;
|
||||
|
||||
Ok(match opcode {
|
||||
Opcode::Add => Instruction::add(left, right, dest),
|
||||
Opcode::Sub => Instruction::sub(left, right, dest),
|
||||
Opcode::And => Instruction::and(left, right, dest),
|
||||
Opcode::Or => Instruction::or(left, right, dest),
|
||||
Opcode::Xor => Instruction::xor(left, right, dest),
|
||||
Opcode::Nand => Instruction::nand(left, right, dest),
|
||||
Opcode::Nor => Instruction::nor(left, right, dest),
|
||||
Opcode::Xnor => Instruction::xnor(left, right, dest),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
fn ins_imm_arithmetic(
|
||||
opcode: Opcode,
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(reg_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
let Some(immediate_token) = args.get(1) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
let Some(dest_token) = args.get(2) else {
|
||||
return Err(AssembleError::MissingArgument(2));
|
||||
};
|
||||
|
||||
let reg = expect_token!(reg_token, Register)?;
|
||||
let immediate = expect_token!(immediate_token, Immediate)? as u16;
|
||||
let dest = expect_token!(dest_token, Register)?;
|
||||
|
||||
Ok(match opcode {
|
||||
Opcode::AddI => Instruction::addi(reg, dest, immediate),
|
||||
Opcode::SubI => Instruction::subi(reg, dest, immediate),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
fn ins_not(args: &[Token]) -> Result<Instruction, AssembleError> {
|
||||
let Some(reg_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
let Some(dest_token) = args.get(1) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
|
||||
let src = expect_token!(reg_token, Register)?;
|
||||
let dest = expect_token!(dest_token, Register)?;
|
||||
Ok(Instruction::not(src, dest))
|
||||
}
|
||||
|
||||
fn ins_interrupt(args: &[Token]) -> Result<Instruction, AssembleError> {
|
||||
let Some(code_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
|
||||
let code = expect_token!(code_token, Immediate)? as u8;
|
||||
Ok(Instruction::int(code))
|
||||
}
|
||||
|
||||
fn build_data_instruction(
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(immediate_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
|
||||
let immediate = expect_token!(immediate_token, Immediate)?;
|
||||
Ok(Instruction::data(immediate))
|
||||
}
|
||||
|
||||
fn build_segment_instruction(
|
||||
args: &[Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(immediate_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
|
||||
let immediate = expect_token!(immediate_token, Immediate)?;
|
||||
Ok(Instruction::data(
|
||||
// mask the first 6 bits for the opcode
|
||||
// this is really deprecated tbh. may remove in future.
|
||||
Opcode::Segment as u32 | (immediate & 0x05FFFFFF),
|
||||
))
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
use common::prelude::Register;
|
||||
|
||||
use crate::assembler::model::{Node, Opcode, Token};
|
||||
use crate::{assembler::AssembleError, expect_token, expect_type, node};
|
||||
|
||||
pub fn expand_pseudo_ops(mut nodes: Vec<Node>, module: u64) -> Result<Vec<Node>, AssembleError> {
|
||||
let mut result = Vec::<Node>::with_capacity(nodes.len());
|
||||
|
||||
for node in &mut nodes {
|
||||
if try_expand(node.clone(), &mut result, module).is_err() {
|
||||
result.push(node.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn try_expand(node: Node, result: &mut Vec<Node>, _module: u64) -> Result<(), AssembleError> {
|
||||
match node.opcode() {
|
||||
Opcode::Func => expand_func(&node, result),
|
||||
Opcode::Return => expand_return(&node, result),
|
||||
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw => {
|
||||
expand_ldx(&node, result)?;
|
||||
}
|
||||
Opcode::Stb | Opcode::Sth | Opcode::Stw => expand_stx(&node, result)?,
|
||||
|
||||
Opcode::Lwi => expand_lwi(&node, result)?,
|
||||
Opcode::Resb | Opcode::Resh | Opcode::Resw => expand_resx(&node, result)?,
|
||||
Opcode::Db | Opcode::Dh | Opcode::Dw => expand_dx(&node, result)?,
|
||||
_ => result.push(node),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Function stack frame initialisation
|
||||
fn expand_func(current: &Node, nodes: &mut Vec<Node>) {
|
||||
let label = current.label();
|
||||
let spr = Token::Register(Register::Spr);
|
||||
let bpr = Token::Register(Register::Bpr);
|
||||
|
||||
nodes.extend(vec![
|
||||
node!(label, Opcode::Push, bpr),
|
||||
node!(None, Opcode::Mov, spr, bpr),
|
||||
]);
|
||||
}
|
||||
|
||||
/// Return from a function
|
||||
fn expand_return(current: &Node, nodes: &mut Vec<Node>) {
|
||||
let label = current.label();
|
||||
let spr = Token::Register(Register::Spr);
|
||||
let ret = Token::Register(Register::Ret);
|
||||
let bpr = Token::Register(Register::Bpr);
|
||||
|
||||
nodes.extend(vec![
|
||||
node!(label, Opcode::Mov, bpr, spr),
|
||||
node!(None, Opcode::Pop, bpr),
|
||||
node!(None, Opcode::Ret),
|
||||
]);
|
||||
}
|
||||
|
||||
fn expand_ldx(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||
let opcode = current.opcode();
|
||||
let args: Vec<Token> = current.args().into_iter().take(3).collect();
|
||||
|
||||
let Some(name) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
|
||||
let Some(reg) = args.get(1) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
|
||||
let Some(offset) = args.get(2) else {
|
||||
return Err(AssembleError::MissingArgument(2));
|
||||
};
|
||||
|
||||
let name = expect_type!(name, Symbol)?;
|
||||
let reg = expect_type!(reg, Register)?;
|
||||
let offset = expect_type!(offset, Immediate)?;
|
||||
|
||||
nodes.extend(vec![
|
||||
node!(current.label(), Opcode::Lli, name, reg),
|
||||
node!(None, Opcode::Lui, name, reg),
|
||||
node!(None, opcode, reg, reg, offset),
|
||||
]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expand_stx(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||
let opcode = current.opcode();
|
||||
|
||||
let args: Vec<Token> = current.args().into_iter().take(3).collect();
|
||||
|
||||
let Some(base) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
|
||||
let Some(dest) = args.get(1) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
|
||||
let Some(offset) = args.get(2) else {
|
||||
return Err(AssembleError::MissingArgument(2));
|
||||
};
|
||||
|
||||
let base = expect_type!(base, Register)?;
|
||||
let dest = expect_type!(dest, Symbol)?;
|
||||
let offset = expect_type!(offset, Immediate)?;
|
||||
let temp = Token::Register(Register::Acc);
|
||||
|
||||
nodes.extend(vec![
|
||||
node!(current.label(), Opcode::Lli, dest, temp),
|
||||
node!(None, Opcode::Lui, dest, temp),
|
||||
node!(None, opcode, base, temp, offset),
|
||||
]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expand_lwi(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||
let Ok(val) = current.arg(0) else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
|
||||
let Ok(reg) = current.arg(1) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
|
||||
let val = expect_type!(val, Symbol, Immediate)?;
|
||||
let reg = expect_type!(reg, Register)?;
|
||||
|
||||
nodes.extend(vec![
|
||||
node!(current.label(), Opcode::Lli, val, reg),
|
||||
node!(None, Opcode::Lui, val, reg),
|
||||
]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expand_resx(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||
let Ok(region_label) = current.arg(0) else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
|
||||
let Ok(size) = current.arg(1) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
|
||||
let region_label = expect_token!(region_label, Symbol)?;
|
||||
let size = expect_token!(size, Immediate)?;
|
||||
|
||||
let units_per = match current.opcode() {
|
||||
Opcode::Resb => 4,
|
||||
Opcode::Resh => 2,
|
||||
Opcode::Resw => 1,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let mut buffer = vec![];
|
||||
// push the inital node with the label
|
||||
for _ in 0..size.div_ceil(units_per) {
|
||||
// push the rest of the nodes
|
||||
buffer.push(node!(None, Opcode::Data, 0));
|
||||
}
|
||||
buffer[0].symbol = Some(region_label);
|
||||
nodes.extend(buffer);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expand_dx(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||
let Ok(region_label) = current.arg(0) else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
|
||||
let region_label = expect_token!(region_label, Symbol)?;
|
||||
let size = match current.opcode() {
|
||||
Opcode::Db => 4,
|
||||
Opcode::Dh => 2,
|
||||
Opcode::Dw => 1,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let mut buffer = vec![];
|
||||
|
||||
let mut args = current.args();
|
||||
let _label = args.remove(0);
|
||||
|
||||
for word in process_dx_data(args, size)? {
|
||||
buffer.push(node!(None, Opcode::Data, Token::Immediate(word)));
|
||||
}
|
||||
buffer[0].symbol = Some(region_label);
|
||||
|
||||
nodes.extend(buffer);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_dx_data(args: Vec<Token>, size: usize) -> Result<Vec<u32>, AssembleError> {
|
||||
assert!(matches!(size, 1 | 2 | 4));
|
||||
|
||||
let mut buffer = Vec::<u8>::new();
|
||||
|
||||
// Process each token
|
||||
for token in args {
|
||||
match token {
|
||||
Token::StringLit(mut s) => {
|
||||
s.push('\0');
|
||||
// Split string into chars and write as bytes
|
||||
for ch in s.chars() {
|
||||
// Convert char to bytes (UTF-8 encoding)
|
||||
let mut char_buf = [0u8; 4];
|
||||
let char_bytes = ch.encode_utf8(&mut char_buf);
|
||||
buffer.extend_from_slice(char_bytes.as_bytes());
|
||||
}
|
||||
}
|
||||
Token::Immediate(value) => {
|
||||
// Split u32 into bytes (little-endian)
|
||||
buffer.extend_from_slice(&value.to_le_bytes());
|
||||
}
|
||||
_ => {
|
||||
return Err(AssembleError::Generic);
|
||||
}
|
||||
}
|
||||
|
||||
// Pad buffer to alignment boundary with zeros
|
||||
let remainder = buffer.len() % size;
|
||||
if remainder != 0 {
|
||||
let padding = size - remainder;
|
||||
buffer.resize(buffer.len() + padding, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert byte buffer to u32 chunks
|
||||
// Pad final buffer to u32 boundary if needed
|
||||
let remainder = buffer.len() % 4;
|
||||
if remainder != 0 {
|
||||
let padding = 4 - remainder;
|
||||
buffer.resize(buffer.len() + padding, 0);
|
||||
}
|
||||
|
||||
// Convert bytes to u32s efficiently using chunks_exact
|
||||
let result = buffer
|
||||
.chunks_exact(4)
|
||||
.map(|chunk| {
|
||||
// Convert 4 bytes to u32 (little-endian)
|
||||
u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::assembler::AssembleError;
|
||||
use crate::assembler::model::{Module, Opcode, Symbol, Token};
|
||||
use common::prelude::Register;
|
||||
|
||||
pub fn lexer(mut program: String, module: u64) -> Result<Vec<Token>, AssembleError> {
|
||||
let mut tokens = Vec::new();
|
||||
|
||||
let lines = program.lines();
|
||||
let mut literal = String::new();
|
||||
|
||||
for line in lines {
|
||||
for (i, token) in line.split_whitespace().enumerate() {
|
||||
if token.starts_with("//") {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(stripped) = token.strip_prefix('"') {
|
||||
literal.push_str(stripped);
|
||||
}
|
||||
|
||||
if !literal.is_empty() {
|
||||
if !token.starts_with('"') {
|
||||
if i > 0 {
|
||||
literal.push(' ');
|
||||
}
|
||||
literal.push_str(token);
|
||||
}
|
||||
|
||||
if token.ends_with('"') {
|
||||
literal.pop(); // remove the closing quote
|
||||
|
||||
tokens.push(Token::StringLit(literal));
|
||||
literal = String::new();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let token = token.trim_end_matches(',');
|
||||
if token.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(token) = parse_register(token)? {
|
||||
tokens.push(token);
|
||||
} else if let Some(token) = parse_opcode(token)? {
|
||||
tokens.push(token);
|
||||
} else if let Some(token) = parse_hex(token)? {
|
||||
tokens.push(token);
|
||||
} else if let Some(token) = parse_octal(token)? {
|
||||
tokens.push(token);
|
||||
} else if let Some(token) = parse_binary(token)? {
|
||||
tokens.push(token);
|
||||
} else if let Some(token) = parse_decimal(token)? {
|
||||
tokens.push(token);
|
||||
} else if let Some(token) = parse_label(token, module)? {
|
||||
tokens.push(token);
|
||||
} else if let Some(token) = parse_symbol(token, module)? {
|
||||
tokens.push(token);
|
||||
} else {
|
||||
return Err(AssembleError::Generic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
pub fn parse_register(token: &str) -> Result<Option<Token>, AssembleError> {
|
||||
Ok(Register::from_str(token).map(Token::Register).ok())
|
||||
}
|
||||
|
||||
pub fn parse_opcode(token: &str) -> Result<Option<Token>, AssembleError> {
|
||||
Ok(Opcode::from_str(token).ok().map(Token::Opcode))
|
||||
}
|
||||
|
||||
pub fn parse_hex(token: &str) -> Result<Option<Token>, AssembleError> {
|
||||
if (token.len() < 3) | !token.starts_with("0x") {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let Some(lit) = &token.get(2..) else {
|
||||
return Err(AssembleError::InvalidArg);
|
||||
};
|
||||
|
||||
u32::from_str_radix(lit, 16).map_or(Err(AssembleError::Generic), |value| {
|
||||
Ok(Some(Token::Immediate(value)))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_octal(token: &str) -> Result<Option<Token>, AssembleError> {
|
||||
if (token.len() < 3) | !token.starts_with("0o") {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let Some(lit) = &token.get(2..) else {
|
||||
return Err(AssembleError::InvalidArg);
|
||||
};
|
||||
|
||||
u32::from_str_radix(lit, 8).map_or(Err(AssembleError::Generic), |value| {
|
||||
Ok(Some(Token::Immediate(value)))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_binary(token: &str) -> Result<Option<Token>, AssembleError> {
|
||||
if (token.len() < 3) | !token.starts_with("0b") {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let Some(lit) = &token.get(2..) else {
|
||||
return Err(AssembleError::InvalidArg);
|
||||
};
|
||||
|
||||
u32::from_str_radix(lit, 2).map_or(Err(AssembleError::Generic), |value| {
|
||||
Ok(Some(Token::Immediate(value)))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_decimal(token: &str) -> Result<Option<Token>, AssembleError> {
|
||||
let Ok(tok) = token.parse::<u32>() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(Some(Token::Immediate(tok)))
|
||||
}
|
||||
|
||||
pub fn parse_label(token: &str, module: u64) -> Result<Option<Token>, AssembleError> {
|
||||
if token.ends_with(':') {
|
||||
Ok(Some(Token::Symbol(Symbol {
|
||||
name: token[0..token.len() - 1].to_string(),
|
||||
module: Module::Resolved(module),
|
||||
})))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_symbol(token: &str, module: u64) -> Result<Option<Token>, AssembleError> {
|
||||
let Some(tokc) = token.chars().next() else {
|
||||
return Err(AssembleError::Generic); // TODO: What is this error?
|
||||
};
|
||||
|
||||
if tokc.is_numeric() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut split = token.splitn(2, "::");
|
||||
let Some(symbol1) = split.next() else {
|
||||
return Err(AssembleError::InvalidArg);
|
||||
};
|
||||
let symbol1 = symbol1.to_string();
|
||||
|
||||
if let Some(symbol2) = split.next() {
|
||||
Ok(Some(Token::Symbol(Symbol {
|
||||
name: symbol2.to_string(),
|
||||
module: Module::Unresolved(symbol1),
|
||||
})))
|
||||
} else {
|
||||
Ok(Some(Token::Symbol(Symbol {
|
||||
name: symbol1,
|
||||
module: Module::Resolved(module),
|
||||
})))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
//! Macros used throughout the assembler
|
||||
|
||||
use crate::assembler::model::{Node, Opcode, Symbol, Token};
|
||||
/// Parse DSA assembly code with optional formatting
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rs
|
||||
/// use assembler::macros::dsa;
|
||||
/// // With formatting:
|
||||
/// let nodes = dsa!(hash, "mov r1, {}", 42)?;
|
||||
///
|
||||
/// // Without formatting:
|
||||
/// let nodes = dsa!(hash, "mov r1, 42")?;
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! dsa {
|
||||
// Version with formatting arguments
|
||||
($hash:expr, $input:expr, $($args:expr),+) => {{
|
||||
let input = format!($input, $($args),+);
|
||||
let tokens = $crate::lexer::lexer(input, $hash)?;
|
||||
let parsed = $crate::parser::Parser::parse_nodes(tokens)?;
|
||||
parsed
|
||||
}};
|
||||
// Version without formatting
|
||||
($hash:expr, $input:expr) => {{
|
||||
let input = String::from($input);
|
||||
let tokens = $crate::lexer::lexer(input, $hash)?;
|
||||
let parsed = $crate::parser::Parser::parse_nodes(tokens)?;
|
||||
parsed
|
||||
}};
|
||||
}
|
||||
|
||||
/// Creates a new Node with the given symbol, opcode, and tokens
|
||||
#[macro_export]
|
||||
macro_rules! node {
|
||||
($symbol: expr, $opcode: expr, args: $tokens: expr) => {
|
||||
$crate::assembler::model::Node::new($symbol.clone(), $opcode.clone(), $tokens.clone())
|
||||
};
|
||||
|
||||
($symbol: expr, $opcode: expr, $($tokens: expr),+) => {
|
||||
$crate::assembler::model::Node::new(
|
||||
$symbol.clone(),
|
||||
$opcode.clone(),
|
||||
vec![$(node!(@convert_token $tokens)),+]
|
||||
)
|
||||
};
|
||||
|
||||
($symbol: expr, $opcode: expr) => {
|
||||
$crate::assembler::model::Node::new(
|
||||
$symbol.clone(),
|
||||
$opcode.clone(),
|
||||
Vec::new()
|
||||
)
|
||||
};
|
||||
|
||||
(@convert_token $token: literal) => {
|
||||
$crate::assembler::model::Token::Immediate($token)
|
||||
};
|
||||
|
||||
(@convert_token $token: expr) => {
|
||||
$token.clone()
|
||||
};
|
||||
}
|
||||
|
||||
/// Extracts a specific token type from a token
|
||||
#[macro_export]
|
||||
macro_rules! expect_token {
|
||||
($token:expr, Symbol) => {
|
||||
match $token {
|
||||
$crate::assembler::model::Token::Symbol(value) => Ok(value.clone()),
|
||||
other => Err($crate::assembler::AssembleError::UnexpectedToken(
|
||||
other.clone(),
|
||||
$crate::assembler::model::TokenType::Symbol,
|
||||
)),
|
||||
}
|
||||
};
|
||||
($token:expr, Register) => {
|
||||
match $token {
|
||||
$crate::assembler::model::Token::Register(value) => Ok(value.clone()),
|
||||
other => Err($crate::assembler::AssembleError::UnexpectedToken(
|
||||
other.clone(),
|
||||
$crate::assembler::model::TokenType::Register,
|
||||
)),
|
||||
}
|
||||
};
|
||||
($token:expr, Immediate) => {
|
||||
match $token {
|
||||
$crate::assembler::model::Token::Immediate(value) => Ok(value.clone()),
|
||||
other => Err($crate::assembler::AssembleError::UnexpectedToken(
|
||||
other.clone(),
|
||||
$crate::assembler::model::TokenType::Immediate,
|
||||
)),
|
||||
}
|
||||
};
|
||||
($token:expr, StringLit) => {
|
||||
match $token {
|
||||
$crate::assembler::model::Token::StringLit(value) => Ok(value.clone()),
|
||||
other => Err($crate::assembler::AssembleError::UnexpectedToken(
|
||||
other.clone(),
|
||||
$crate::assembler::model::TokenType::StringLit,
|
||||
)),
|
||||
}
|
||||
};
|
||||
($token:expr, Opcode) => {
|
||||
match $token {
|
||||
$crate::assembler::model::Token::Opcode(value) => Ok(value.clone()),
|
||||
other => Err($crate::assembler::AssembleError::UnexpectedToken(
|
||||
other.clone(),
|
||||
$crate::assembler::model::TokenType::Opcode,
|
||||
)),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Checks if a token matches any of the specified types
|
||||
#[macro_export]
|
||||
macro_rules! expect_type {
|
||||
($token:expr, $($variant:ident),+) => {{
|
||||
let token = $token;
|
||||
match &token {
|
||||
$(
|
||||
$crate::assembler::model::Token::$variant(_) => Ok(token.clone()),
|
||||
)+
|
||||
other => {
|
||||
let expected_type = expect_type!(@get_first_type $($variant),+);
|
||||
Err($crate::assembler::AssembleError::UnexpectedToken(
|
||||
other.clone().clone(),
|
||||
expected_type,
|
||||
))
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
(@get_first_type Symbol $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Symbol };
|
||||
(@get_first_type Register $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Register };
|
||||
(@get_first_type Immediate $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Immediate };
|
||||
(@get_first_type StringLit $(, $rest:ident)*) => { $crate::assembler::model::TokenType::StringLit };
|
||||
(@get_first_type Opcode $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Opcode };
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
#![allow(dead_code, unused)]
|
||||
|
||||
use crate::assembler::resolver2::{SectionId, resolve_address, resolve_dependencies, resolve_symbols, split_sections};
|
||||
use common::process::{ProcessAgent, TraceableProcess};
|
||||
use dsa_formats::binary_dse::{DseExecutable, SymbolEntry};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fmt, fs,
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
path::{Path, PathBuf},
|
||||
sync::
|
||||
mpsc::{self},
|
||||
};
|
||||
|
||||
// Module declarations
|
||||
#[macro_use]
|
||||
pub mod macros;
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod codegen;
|
||||
pub mod expand;
|
||||
|
||||
mod lexer;
|
||||
mod model;
|
||||
pub mod parser;
|
||||
// pub mod resolver;
|
||||
pub mod resolver2;
|
||||
|
||||
// Re-exports
|
||||
pub use self::{
|
||||
codegen::codegen,
|
||||
expand::expand_pseudo_ops,
|
||||
lexer::lexer,
|
||||
model::{Module, Node, Opcode, Symbol, Token, TokenType},
|
||||
parser::{Parser, Program},
|
||||
// resolver::{create_sections, resolve_dependencies, resolve_symbols},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Assembler {
|
||||
src_path: PathBuf,
|
||||
result_tx: mpsc::Sender<Result<Vec<u8>, AssembleError>>,
|
||||
result_rx: Option<mpsc::Receiver<Result<Vec<u8>, AssembleError>>>,
|
||||
is_running: bool,
|
||||
}
|
||||
|
||||
impl Assembler {
|
||||
#[must_use]
|
||||
pub fn new(src_path: impl Into<PathBuf>) -> Self {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
Self {
|
||||
src_path: src_path.into(),
|
||||
result_tx: tx,
|
||||
result_rx: Some(rx),
|
||||
is_running: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TraceableProcess for Assembler {
|
||||
type Output = DseExecutable;
|
||||
type Error = AssembleError;
|
||||
|
||||
fn run(mut self: Box<Self>, ctx: ProcessAgent) -> Result<Self::Output, Self::Error> {
|
||||
assemble(&self.src_path, &ctx)
|
||||
}
|
||||
}
|
||||
|
||||
fn assemble(src: &Path, ctx: &ProcessAgent) -> Result<DseExecutable, AssembleError> {
|
||||
let mut modules = HashSet::new();
|
||||
let mut program = Program::new();
|
||||
|
||||
prepare_dependency(src, &mut modules, &mut program, &ctx)?;
|
||||
|
||||
let nodes = program.nodes.clone();
|
||||
|
||||
// for node in &nodes {
|
||||
// ctx.log(format!("{:?}", node));
|
||||
// }
|
||||
|
||||
let (data_nodes, mut text_nodes) = split_sections(nodes);
|
||||
let symbol_table = resolve_symbols(&data_nodes, &mut text_nodes)?;
|
||||
let text_len = text_nodes.len() as u32;
|
||||
|
||||
ctx.log("Generating assembly output...");
|
||||
|
||||
// println!("Generating assembly output...");
|
||||
let instructions = codegen(text_nodes, ctx)?;
|
||||
let text: Vec<u32> = instructions
|
||||
.iter()
|
||||
.map(|i| i.0).collect();
|
||||
|
||||
let data = build_data_bytes(&data_nodes)?;
|
||||
|
||||
// Build string table + SymbolEntry list from the resolved symbol table
|
||||
let mut string_table = Vec::new();
|
||||
let mut symbols = Vec::new();
|
||||
for (symbol, entry) in &symbol_table {
|
||||
let name_offset = string_table.len() as u32;
|
||||
string_table.extend_from_slice(symbol.name.as_bytes());
|
||||
string_table.push(0); // null terminator
|
||||
|
||||
symbols.push(SymbolEntry {
|
||||
name_offset,
|
||||
value: resolve_address(*entry, text_len),
|
||||
section: match entry.1 {
|
||||
SectionId::Text => 2,
|
||||
SectionId::Data => 1,
|
||||
},
|
||||
binding: 1, // global — refine later if adding local/weak
|
||||
_pad: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Entry point: first symbol to jump to is '_init'
|
||||
let entry_point = symbol_table
|
||||
.iter()
|
||||
.find(|(sym, _)| sym.name == "_init")
|
||||
.map(|(_, entry)| resolve_address(*entry, text_len))
|
||||
.ok_or(AssembleError::UndefinedSymbol(Symbol {
|
||||
name: "_init".to_string(),
|
||||
module: Module::Resolved(0),
|
||||
}))?;
|
||||
|
||||
ctx.log("Compilation Successful");
|
||||
|
||||
Ok(DseExecutable::new(
|
||||
symbols.as_slice(),
|
||||
&string_table,
|
||||
&data,
|
||||
&text,
|
||||
entry_point,
|
||||
).unwrap())
|
||||
}
|
||||
|
||||
fn build_data_bytes(data_nodes: &[Node]) -> Result<Vec<u8>, AssembleError> {
|
||||
let mut bytes = Vec::with_capacity(data_nodes.len() * 4);
|
||||
for node in data_nodes {
|
||||
let Token::Immediate(value) = node.arg(0)? else {
|
||||
return Err(AssembleError::InvalidArg);
|
||||
};
|
||||
bytes.extend_from_slice(&value.to_le_bytes());
|
||||
}
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
|
||||
fn prepare_dependency(
|
||||
path: &Path,
|
||||
modules: &mut HashSet<u64>,
|
||||
program: &mut Program,
|
||||
ctx: &ProcessAgent,
|
||||
) -> Result<(), AssembleError> {
|
||||
let filename = path
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.expect("Failed to get file name from path");
|
||||
|
||||
if let Ok(path) = path.canonicalize() {
|
||||
ctx.log(format!("{:20} {:20} [{}]", "Building", filename, path.display()));
|
||||
}
|
||||
|
||||
let src =
|
||||
fs::read_to_string(path).map_err(|_| AssembleError::InvalidFile(path.to_path_buf()))?;
|
||||
let file_hash = quick_hash(path);
|
||||
|
||||
ctx.log(format!("{:20} {:20}", "Tokenising", filename));
|
||||
let tokens = lexer::lexer(src, file_hash)?;
|
||||
// let tokens = Lexer::new(src, file_hash).run()?;
|
||||
|
||||
ctx.log(format!("{:20} {:20}", "Parsing", filename));
|
||||
let parsed = Parser::parse_nodes(tokens)?;
|
||||
|
||||
ctx.log(format!("{:20} {:20}", "Resolving Deps", filename));
|
||||
// Get the parent directory of the source file to use as the base directory
|
||||
let base_dir = path
|
||||
.parent()
|
||||
.ok_or_else(|| AssembleError::InvalidFile(path.to_path_buf()))?;
|
||||
let mut nodes = expand_pseudo_ops(parsed, file_hash)?;
|
||||
nodes = resolve_dependencies(nodes, base_dir)?;
|
||||
|
||||
let deps = Parser::get_dependencies(&nodes, path)?;
|
||||
|
||||
ctx.log(format!("{:20} {:20}", "Expanding Pseudo-ops", filename));
|
||||
|
||||
// add a section instruction
|
||||
nodes.insert(
|
||||
0,
|
||||
node!(None, Opcode::Segment, Token::Immediate(file_hash as u32)),
|
||||
);
|
||||
|
||||
program.add_module(nodes);
|
||||
|
||||
for dep in deps {
|
||||
ctx.log(format!(
|
||||
"{:20} {:20}",
|
||||
"Including",
|
||||
dep.file_name()
|
||||
.and_then(|f| f.to_str())
|
||||
.expect("Dependency path has no file name or is not valid UTF-8")
|
||||
));
|
||||
|
||||
let dep_hash = quick_hash(&dep);
|
||||
if modules.insert(dep_hash) {
|
||||
prepare_dependency(dep.as_path(), modules, program, &ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AssembleError {
|
||||
Generic,
|
||||
UnexpectedEof,
|
||||
InvalidFile(PathBuf),
|
||||
UnexpectedToken(Token, TokenType),
|
||||
InvalidArg,
|
||||
UndefinedSymbol(Symbol),
|
||||
/// Contains the nth element missing from the instruction.
|
||||
MissingArgument(u8),
|
||||
}
|
||||
|
||||
impl std::error::Error for AssembleError {}
|
||||
|
||||
impl fmt::Display for AssembleError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Generic => write!(f, "Generic error"),
|
||||
Self::UnexpectedToken(tok, expected) => {
|
||||
write!(f, "Unexpected token {tok:?}, expected {expected:?}")
|
||||
}
|
||||
Self::UnexpectedEof => write!(f, "Unexpected end of file"),
|
||||
Self::InvalidFile(path) => write!(f, "Invalid file `{}`", path.display()),
|
||||
Self::InvalidArg => write!(f, "Invalid argument"),
|
||||
Self::UndefinedSymbol(symbol) => {
|
||||
write!(f, "Undefined symbol {symbol}")
|
||||
}
|
||||
Self::MissingArgument(n) => {
|
||||
write!(f, "Missing argument #{n} from instruction arguments.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn quick_hash(value: &Path) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
value
|
||||
.canonicalize()
|
||||
.expect("Failed to canonicalize path for quick_hash")
|
||||
.to_str()
|
||||
.hash(&mut hasher);
|
||||
|
||||
hasher.finish()
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
use common::prelude::Register;
|
||||
|
||||
use crate::assembler::AssembleError;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Node {
|
||||
pub symbol: Option<Symbol>,
|
||||
pub opcode: Opcode,
|
||||
pub tokens: Vec<Token>,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
#[must_use]
|
||||
pub const fn new(symbol: Option<Symbol>, opcode: Opcode, tokens: Vec<Token>) -> Self {
|
||||
Self {
|
||||
symbol,
|
||||
opcode,
|
||||
tokens,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn label(&self) -> Option<Symbol> {
|
||||
self.symbol.clone()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn opcode(&self) -> Opcode {
|
||||
self.opcode
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn args(&self) -> Vec<Token> {
|
||||
self.tokens.clone()
|
||||
}
|
||||
|
||||
pub fn arg(&self, index: usize) -> Result<Token, AssembleError> {
|
||||
self.args()
|
||||
.get(index)
|
||||
.cloned()
|
||||
.ok_or(AssembleError::InvalidArg)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Node {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let symbol = self
|
||||
.label()
|
||||
.as_ref()
|
||||
.map_or_else(String::new, |symbol| format!("{symbol}:\n"));
|
||||
|
||||
let args = self
|
||||
.args()
|
||||
.into_iter()
|
||||
.map(|arg| arg.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
|
||||
write!(
|
||||
f,
|
||||
"\x1b[93m{} \t\x1b[94m{} \x1b[37m{} \x1b[0m",
|
||||
symbol,
|
||||
self.opcode(),
|
||||
args,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Symbol {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} [ID:{}]", self.name, self.module)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Module {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Unresolved(name) => write!(f, "{name}"),
|
||||
Self::Resolved(name) => write!(f, "{name}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Opcode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Nop => write!(f, "nop"),
|
||||
Self::Mov => write!(f, "mov"),
|
||||
Self::CMov => write!(f, "movs"),
|
||||
Self::Ldb => write!(f, "ldb"),
|
||||
Self::Ldbs => write!(f, "ldbs"),
|
||||
Self::Ldh => write!(f, "ldh"),
|
||||
Self::Ldhs => write!(f, "ldhs"),
|
||||
Self::Ldw => write!(f, "ldw"),
|
||||
Self::Stb => write!(f, "stb"),
|
||||
Self::Sth => write!(f, "sth"),
|
||||
Self::Stw => write!(f, "stw"),
|
||||
Self::Lli => write!(f, "lli"),
|
||||
Self::Lui => write!(f, "lui"),
|
||||
|
||||
Self::Jmp => write!(f, "jmp"),
|
||||
Self::Jez => write!(f, "jez"),
|
||||
Self::Jnz => write!(f, "jnz"),
|
||||
Self::Jic => write!(f, "jic"),
|
||||
Self::Jnc => write!(f, "jnc"),
|
||||
|
||||
Self::Ieq => write!(f, "ieq"),
|
||||
Self::Ine => write!(f, "ine"),
|
||||
Self::Igt => write!(f, "igt"),
|
||||
Self::Ige => write!(f, "ige"),
|
||||
Self::Ilt => write!(f, "ilt"),
|
||||
Self::Ile => write!(f, "ile"),
|
||||
|
||||
Self::Shl => write!(f, "shl"),
|
||||
Self::Shr => write!(f, "shr"),
|
||||
Self::Add => write!(f, "add"),
|
||||
Self::Sub => write!(f, "sub"),
|
||||
Self::AddI => write!(f, "addi"),
|
||||
Self::SubI => write!(f, "subi"),
|
||||
|
||||
Self::And => write!(f, "and"),
|
||||
Self::Or => write!(f, "or"),
|
||||
Self::Not => write!(f, "not"),
|
||||
Self::Xor => write!(f, "xor"),
|
||||
Self::Nand => write!(f, "nand"),
|
||||
Self::Nor => write!(f, "nor"),
|
||||
Self::Xnor => write!(f, "xnor"),
|
||||
|
||||
Self::Int => write!(f, "int"),
|
||||
Self::IRet => write!(f, "irt"),
|
||||
Self::Hlt => write!(f, "hlt"),
|
||||
|
||||
Self::Db => write!(f, "db"),
|
||||
Self::Dh => write!(f, "dh"),
|
||||
Self::Dw => write!(f, "dw"),
|
||||
Self::Resb => write!(f, "resb"),
|
||||
Self::Resh => write!(f, "resh"),
|
||||
Self::Resw => write!(f, "resw"),
|
||||
|
||||
Self::Push => write!(f, "push"),
|
||||
Self::Pop => write!(f, "pop"),
|
||||
Self::Lwi => write!(f, "lwi"),
|
||||
|
||||
Self::Func => write!(f, "func"),
|
||||
Self::Call => write!(f, "call"),
|
||||
Self::Ret => write!(f, "ret"),
|
||||
Self::Return => write!(f, "return"),
|
||||
|
||||
Self::Include => write!(f, "include"),
|
||||
Self::Data => write!(f, "data"),
|
||||
Self::Segment => write!(f, "[SEGMENT]"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq)]
|
||||
pub struct Symbol {
|
||||
pub name: String,
|
||||
pub module: Module,
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Symbol {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.name.hash(state);
|
||||
self.module.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Symbol {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name == other.name && self.module == other.module
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Module {
|
||||
Resolved(u64),
|
||||
Unresolved(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Token {
|
||||
Symbol(Symbol),
|
||||
Register(Register),
|
||||
Immediate(u32),
|
||||
StringLit(String),
|
||||
CharLit(char),
|
||||
Opcode(Opcode),
|
||||
}
|
||||
|
||||
impl fmt::Display for Token {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Symbol(symbol) => write!(f, "{symbol}"),
|
||||
Self::Register(register) => write!(f, "{register}",),
|
||||
Self::Immediate(immediate) => write!(f, "{immediate}",),
|
||||
Self::StringLit(string_lit) => write!(f, "{string_lit}",),
|
||||
Self::CharLit(char_lit) => write!(f, "{char_lit}",),
|
||||
Self::Opcode(opcode) => write!(f, "{opcode}",),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum TokenType {
|
||||
Symbol,
|
||||
Register,
|
||||
Immediate,
|
||||
StringLit,
|
||||
CharLit,
|
||||
Opcode,
|
||||
}
|
||||
|
||||
impl TokenType {
|
||||
#[must_use]
|
||||
pub const fn from_token(token: &Token) -> Self {
|
||||
match token {
|
||||
Token::Symbol(_) => Self::Symbol,
|
||||
Token::Register(_) => Self::Register,
|
||||
Token::Immediate(_) => Self::Immediate,
|
||||
Token::StringLit(_) => Self::StringLit,
|
||||
Token::CharLit(_) => Self::CharLit,
|
||||
Token::Opcode(_) => Self::Opcode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Opcode {
|
||||
// Real instructions (0x00-0x26)
|
||||
Nop,
|
||||
Mov,
|
||||
CMov,
|
||||
Ldb,
|
||||
Ldbs,
|
||||
Ldh,
|
||||
Ldhs,
|
||||
Ldw,
|
||||
Stb,
|
||||
Sth,
|
||||
Stw,
|
||||
Lli,
|
||||
Lui,
|
||||
|
||||
Jmp,
|
||||
Jez,
|
||||
Jnz,
|
||||
Jic,
|
||||
Jnc,
|
||||
|
||||
Ieq,
|
||||
Ine,
|
||||
Igt,
|
||||
Ige,
|
||||
Ilt,
|
||||
Ile,
|
||||
|
||||
Shl,
|
||||
Shr,
|
||||
Add,
|
||||
Sub,
|
||||
AddI,
|
||||
SubI,
|
||||
|
||||
And,
|
||||
Or,
|
||||
Not,
|
||||
Xor,
|
||||
Nand,
|
||||
Nor,
|
||||
Xnor,
|
||||
|
||||
Int,
|
||||
IRet,
|
||||
Hlt,
|
||||
|
||||
// Function instructions
|
||||
Call,
|
||||
Ret,
|
||||
|
||||
// Stack ops
|
||||
Push,
|
||||
Pop,
|
||||
|
||||
// Pseudo-instructions
|
||||
Db,
|
||||
Dh,
|
||||
Dw,
|
||||
Resb,
|
||||
Resh,
|
||||
Resw,
|
||||
Lwi,
|
||||
Func,
|
||||
Return,
|
||||
|
||||
// meta instructions (these aren't present in the binary as instructions)
|
||||
Include,
|
||||
Data,
|
||||
Segment,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OpcodeFromStrError {
|
||||
InvalidRegister(&'static str),
|
||||
InvalidOpcode(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for OpcodeFromStrError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InvalidRegister(reg) => write!(f, "register does not exist: {reg}"),
|
||||
Self::InvalidOpcode(op) => write!(f, "instruction does not exist: {op}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for OpcodeFromStrError {}
|
||||
|
||||
impl FromStr for Opcode {
|
||||
type Err = OpcodeFromStrError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"nop" => Ok(Self::Nop),
|
||||
"mov" => Ok(Self::Mov),
|
||||
"cmov" => Ok(Self::CMov),
|
||||
"ldb" => Ok(Self::Ldb),
|
||||
"ldbs" => Ok(Self::Ldbs),
|
||||
"ldh" => Ok(Self::Ldh),
|
||||
"ldhs" => Ok(Self::Ldhs),
|
||||
"ldw" => Ok(Self::Ldw),
|
||||
"stb" => Ok(Self::Stb),
|
||||
"sth" => Ok(Self::Sth),
|
||||
"stw" => Ok(Self::Stw),
|
||||
"lli" => Ok(Self::Lli),
|
||||
"lui" => Ok(Self::Lui),
|
||||
|
||||
// comparison
|
||||
"ieq" => Ok(Self::Ieq),
|
||||
"ine" => Ok(Self::Ine),
|
||||
"igt" => Ok(Self::Igt),
|
||||
"ige" => Ok(Self::Ige),
|
||||
"ilt" => Ok(Self::Ilt),
|
||||
"ile" => Ok(Self::Ile),
|
||||
|
||||
// jumps
|
||||
"jmp" => Ok(Self::Jmp),
|
||||
"jez" => Ok(Self::Jez),
|
||||
"jnz" => Ok(Self::Jnz),
|
||||
"jic" => Ok(Self::Jic),
|
||||
"jnc" => Ok(Self::Jnc),
|
||||
|
||||
"shl" => Ok(Self::Shl),
|
||||
"shr" => Ok(Self::Shr),
|
||||
"add" => Ok(Self::Add),
|
||||
"sub" => Ok(Self::Sub),
|
||||
"and" => Ok(Self::And),
|
||||
"or" => Ok(Self::Or),
|
||||
"not" => Ok(Self::Not),
|
||||
"xor" => Ok(Self::Xor),
|
||||
"nand" => Ok(Self::Nand),
|
||||
"nor" => Ok(Self::Nor),
|
||||
"xnor" => Ok(Self::Xnor),
|
||||
"addi" => Ok(Self::AddI),
|
||||
"subi" => Ok(Self::SubI),
|
||||
|
||||
// stack ops
|
||||
"push" => Ok(Self::Push),
|
||||
"pop" => Ok(Self::Pop),
|
||||
|
||||
// function instructions
|
||||
"call" => Ok(Self::Call),
|
||||
"ret" => Ok(Self::Ret),
|
||||
|
||||
"int" => Ok(Self::Int),
|
||||
"irt" | "iret" => Ok(Self::IRet),
|
||||
"hlt" => Ok(Self::Hlt),
|
||||
|
||||
// pseudoinstructions
|
||||
"func" => Ok(Self::Func),
|
||||
"return" => Ok(Self::Return),
|
||||
"lwi" => Ok(Self::Lwi),
|
||||
|
||||
// directives
|
||||
"include" => Ok(Self::Include),
|
||||
"db" => Ok(Self::Db),
|
||||
"dh" => Ok(Self::Dh),
|
||||
"dw" => Ok(Self::Dw),
|
||||
"resb" => Ok(Self::Resb),
|
||||
"resh" => Ok(Self::Resh),
|
||||
"resw" => Ok(Self::Resw),
|
||||
|
||||
// function pseudoinstructions
|
||||
_ => Err(OpcodeFromStrError::InvalidOpcode(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Opcode {
|
||||
pub const OPCODES: &[&str] = &[
|
||||
// Real instructions (0x00-0x26)
|
||||
"nop", "mov", "movs", "ldb", "ldbs", "ldh", "ldhs", "ldw", "stb", "sth", "stw", "lli",
|
||||
"lui", "jmp", "jeq", "jne", "jgt", "jge", "jlt", "jle", "cmp", "inc", "dec", "shl", "shr",
|
||||
"add", "sub", "and", "or", "not", "xor", "nand", "nor", "xnor", "int", "iret", "hlt",
|
||||
"addi", "subi", "call", "ret", // Pseudo-instructions
|
||||
"db", "dh", "dw", "resb", "resh", "resw", "lwi", "func", "return",
|
||||
// meta instructions
|
||||
"include",
|
||||
];
|
||||
|
||||
#[must_use]
|
||||
pub const fn to_opcode_value(&self) -> Option<u8> {
|
||||
match self {
|
||||
Self::Nop => Some(0x00),
|
||||
Self::Mov => Some(0x01),
|
||||
Self::CMov => Some(0x02),
|
||||
Self::Ldb => Some(0x03),
|
||||
Self::Ldbs => Some(0x04),
|
||||
Self::Ldh => Some(0x05),
|
||||
Self::Ldhs => Some(0x06),
|
||||
Self::Ldw => Some(0x07),
|
||||
Self::Stb => Some(0x08),
|
||||
Self::Sth => Some(0x09),
|
||||
Self::Stw => Some(0x0A),
|
||||
Self::Lli => Some(0x0B),
|
||||
Self::Lui => Some(0x0C),
|
||||
Self::Ieq => Some(0x0D),
|
||||
Self::Ine => Some(0x0E),
|
||||
Self::Ilt => Some(0x0F),
|
||||
Self::Ile => Some(0x10),
|
||||
Self::Igt => Some(0x11),
|
||||
Self::Ige => Some(0x12),
|
||||
Self::Jmp => Some(0x13),
|
||||
Self::Jez => Some(0x14),
|
||||
Self::Jnz => Some(0x15),
|
||||
Self::Jic => Some(0x16),
|
||||
Self::Jnc => Some(0x17),
|
||||
Self::And => Some(0x18),
|
||||
Self::Nand => Some(0x19),
|
||||
Self::Or => Some(0x1A),
|
||||
Self::Nor => Some(0x1B),
|
||||
Self::Xor => Some(0x1C),
|
||||
Self::Xnor => Some(0x1D),
|
||||
Self::Not => Some(0x1E),
|
||||
Self::Add => Some(0x1F),
|
||||
Self::Sub => Some(0x20),
|
||||
Self::Shl => Some(0x21),
|
||||
Self::Shr => Some(0x22),
|
||||
Self::AddI => Some(0x23),
|
||||
Self::SubI => Some(0x24),
|
||||
|
||||
Self::Push => Some(0x25),
|
||||
Self::Pop => Some(0x26),
|
||||
Self::Call => Some(0x27),
|
||||
Self::Ret => Some(0x28),
|
||||
|
||||
Self::Int => Some(0x29),
|
||||
Self::IRet => Some(0x2A),
|
||||
Self::Hlt => Some(0x2B),
|
||||
|
||||
Self::Segment => Some(0x2C),
|
||||
// Pseudo-instructions don't have opcode values
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn is_pseudo_instruction(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Db
|
||||
| Self::Dh
|
||||
| Self::Dw
|
||||
| Self::Resb
|
||||
| Self::Resh
|
||||
| Self::Resw
|
||||
| Self::Lwi
|
||||
| Self::Func
|
||||
| Self::Return
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::assembler::TokenType;
|
||||
use crate::{assembler::AssembleError, expect_token, expect_type, node};
|
||||
|
||||
use crate::assembler::model::{Node, Opcode, Token};
|
||||
use common::prelude::*;
|
||||
|
||||
pub struct Parser {
|
||||
tokens: Vec<Token>,
|
||||
nodes: Vec<Node>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Program {
|
||||
pub nodes: Vec<Node>,
|
||||
}
|
||||
|
||||
impl Program {
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self { nodes: vec![] }
|
||||
}
|
||||
|
||||
pub fn add_module(&mut self, module: Vec<Node>) {
|
||||
self.nodes.extend(module);
|
||||
}
|
||||
|
||||
pub fn parser(&mut self) -> Parser {
|
||||
Parser {
|
||||
tokens: vec![],
|
||||
nodes: self.nodes.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Program {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn parse_nodes(tokens: Vec<Token>) -> Result<Vec<Node>, AssembleError> {
|
||||
let mut self_ = Self {
|
||||
tokens: tokens.into_iter().rev().collect(),
|
||||
nodes: vec![],
|
||||
};
|
||||
|
||||
while !self_.tokens.is_empty() {
|
||||
let ins = self_.parse_instruction()?;
|
||||
self_.nodes.push(ins);
|
||||
}
|
||||
|
||||
Ok(self_.nodes.clone())
|
||||
}
|
||||
|
||||
pub fn get_dependencies(
|
||||
nodes: &Vec<Node>,
|
||||
source_path: &Path,
|
||||
) -> Result<Vec<PathBuf>, AssembleError> {
|
||||
let mut dependencies = Vec::new();
|
||||
// Get the parent directory of the source file to use as the base directory
|
||||
let base_dir = source_path
|
||||
.parent()
|
||||
.ok_or_else(|| AssembleError::InvalidFile(source_path.to_path_buf()))?;
|
||||
|
||||
for node in nodes {
|
||||
if node.opcode() == Opcode::Include {
|
||||
let path_str =
|
||||
expect_token!(node.args().get(1).ok_or(AssembleError::Generic)?, StringLit)?;
|
||||
let path = PathBuf::from(path_str);
|
||||
|
||||
// If the path is not absolute, make it relative to the base directory
|
||||
let full_path = if path.is_absolute() {
|
||||
path
|
||||
} else {
|
||||
base_dir.join(path)
|
||||
};
|
||||
|
||||
dependencies.push(full_path);
|
||||
}
|
||||
}
|
||||
Ok(dependencies)
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines, clippy::cognitive_complexity)]
|
||||
fn parse_instruction(&mut self) -> Result<Node, AssembleError> {
|
||||
if self.tokens.is_empty() {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
// check if the Node starts with a label
|
||||
let label = expect_token!(self.peek_next()?, Symbol).ok();
|
||||
if label.is_some() {
|
||||
self.tokens.pop();
|
||||
}
|
||||
|
||||
let opcode = expect_token!(self.next()?, Opcode)?;
|
||||
let args: Vec<Token>;
|
||||
|
||||
#[allow(clippy::match_same_arms)]
|
||||
match opcode {
|
||||
// R-type instructions
|
||||
Opcode::Mov | Opcode::CMov => {
|
||||
let reg1 = expect_type!(self.next()?, Register, Symbol)?;
|
||||
let reg2 = expect_type!(self.next()?, Register, Symbol)?;
|
||||
args = vec![reg1, reg2];
|
||||
}
|
||||
|
||||
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw => {
|
||||
let base = expect_type!(self.next()?, Register, Symbol)?;
|
||||
let dest = expect_type!(self.next()?, Register)?;
|
||||
|
||||
let offset = match self.peek_next() {
|
||||
Ok(next) if expect_type!(next.clone(), Immediate).is_ok() => self.next()?,
|
||||
_ => Token::Immediate(0),
|
||||
};
|
||||
|
||||
args = vec![base, dest, offset];
|
||||
}
|
||||
Opcode::Stb | Opcode::Sth | Opcode::Stw => {
|
||||
let base = expect_type!(self.next()?, Register)?;
|
||||
let dest = expect_type!(self.next()?, Register, Symbol)?;
|
||||
|
||||
let offset = match self.peek_next() {
|
||||
Ok(next) if expect_type!(next.clone(), Immediate).is_ok() => self.next()?,
|
||||
_ => Token::Immediate(0),
|
||||
};
|
||||
args = vec![base, dest, offset];
|
||||
}
|
||||
|
||||
Opcode::Add
|
||||
| Opcode::Sub
|
||||
| Opcode::And
|
||||
| Opcode::Or
|
||||
| Opcode::Xor
|
||||
| Opcode::Nand
|
||||
| Opcode::Nor
|
||||
| Opcode::Xnor => {
|
||||
let src1 = expect_type!(self.next()?, Register, Symbol)?;
|
||||
let src2 = expect_type!(self.next()?, Register, Symbol)?;
|
||||
let dest = expect_type!(self.next()?, Register, Symbol)?;
|
||||
args = vec![src1, src2, dest];
|
||||
}
|
||||
|
||||
Opcode::Not => {
|
||||
let src = expect_type!(self.next()?, Register, Symbol)?;
|
||||
let dest = expect_type!(self.next()?, Register, Symbol)?;
|
||||
args = vec![src, dest];
|
||||
}
|
||||
Opcode::Shl | Opcode::Shr => {
|
||||
let src = expect_type!(self.next()?, Register, Symbol)?;
|
||||
|
||||
// First operand after src: could be immediate or register
|
||||
let first = self.next()?;
|
||||
|
||||
let (r_shamt, i_shamt) = match first {
|
||||
Token::Register(_) => (
|
||||
first,
|
||||
if let Ok(tok) = self.peek_next() {
|
||||
if expect_type!(tok, Immediate).is_ok() {
|
||||
self.next()?
|
||||
} else {
|
||||
Token::Immediate(0)
|
||||
}
|
||||
} else {
|
||||
Token::Immediate(0)
|
||||
},
|
||||
),
|
||||
Token::Immediate(_) => (Token::Register(Register::Zero), first),
|
||||
_ => {
|
||||
return Err(AssembleError::UnexpectedToken(first, TokenType::Immediate));
|
||||
}
|
||||
};
|
||||
|
||||
let dest = if let Ok(tok) = self.peek_next() {
|
||||
if expect_type!(tok, Register).is_ok() {
|
||||
self.next()?
|
||||
} else {
|
||||
src.clone() // Default to src if no dest specified
|
||||
}
|
||||
} else {
|
||||
src.clone() // Default to src if no dest specified
|
||||
};
|
||||
|
||||
args = vec![src, r_shamt, i_shamt, dest];
|
||||
}
|
||||
|
||||
Opcode::Include => {
|
||||
let mod_name = expect_type!(self.next()?, Symbol)?;
|
||||
let path = expect_type!(self.next()?, StringLit)?;
|
||||
args = vec![mod_name, path];
|
||||
}
|
||||
|
||||
// Unconditional jump
|
||||
Opcode::Jmp | Opcode::Jic | Opcode::Jnc => {
|
||||
let imm = expect_type!(self.next()?, Immediate, Symbol)?;
|
||||
let offset = match self.peek_next() {
|
||||
Ok(token) => {
|
||||
if expect_type!(token, Register).is_ok() {
|
||||
self.next()?
|
||||
} else {
|
||||
Token::Register(Register::Zero)
|
||||
}
|
||||
}
|
||||
Err(_) => Token::Register(Register::Zero),
|
||||
};
|
||||
args = vec![imm, offset];
|
||||
}
|
||||
|
||||
Opcode::Ieq | Opcode::Ine | Opcode::Ilt | Opcode::Igt | Opcode::Ile | Opcode::Ige => {
|
||||
let src1 = expect_type!(self.next()?, Register, Symbol)?;
|
||||
let src2 = expect_type!(self.next()?, Register, Symbol)?;
|
||||
let dest = expect_type!(self.next()?, Register, Symbol)?;
|
||||
args = vec![src1, src2, dest];
|
||||
}
|
||||
|
||||
Opcode::Jez | Opcode::Jnz => {
|
||||
let condition = expect_type!(self.next()?, Register)?;
|
||||
let imm = expect_type!(self.next()?, Immediate, Symbol)?;
|
||||
let offset = match self.peek_next() {
|
||||
Ok(token) => {
|
||||
if expect_type!(token, Register).is_ok() {
|
||||
self.next()?
|
||||
} else {
|
||||
Token::Register(Register::Zero)
|
||||
}
|
||||
}
|
||||
Err(_) => Token::Register(Register::Zero),
|
||||
};
|
||||
args = vec![condition, imm, offset];
|
||||
}
|
||||
|
||||
Opcode::Call => {
|
||||
let addr = expect_type!(self.next()?, Symbol)?;
|
||||
args = vec![addr];
|
||||
}
|
||||
|
||||
// I-type instructions
|
||||
Opcode::Lui | Opcode::Lli | Opcode::Lwi => {
|
||||
let imm = expect_type!(self.next()?, Immediate, Symbol)?;
|
||||
let reg = expect_type!(self.next()?, Register)?;
|
||||
args = vec![imm, reg];
|
||||
}
|
||||
|
||||
// Immediate Arithmetic
|
||||
Opcode::AddI | Opcode::SubI => {
|
||||
let reg = expect_type!(self.next()?, Register)?;
|
||||
let imm = expect_type!(self.next()?, Immediate)?;
|
||||
let reg2 = if expect_type!(self.peek_next()?, Register).is_ok() {
|
||||
self.next()?
|
||||
} else {
|
||||
reg.clone()
|
||||
};
|
||||
args = vec![reg, imm, reg2];
|
||||
}
|
||||
|
||||
// D-type pseudoinstructions (data definition)
|
||||
Opcode::Resb | Opcode::Resh | Opcode::Resw => {
|
||||
let name = expect_type!(self.next()?, Symbol)?;
|
||||
let num = expect_type!(self.next()?, Immediate)?;
|
||||
args = vec![name, num];
|
||||
}
|
||||
|
||||
Opcode::Db | Opcode::Dh | Opcode::Dw => {
|
||||
args = self.parse_data_definition(opcode)?;
|
||||
}
|
||||
|
||||
// E-type pseudoinstructions (stack operations)
|
||||
Opcode::Push | Opcode::Pop => {
|
||||
let reg = expect_type!(self.next()?, Register, Symbol)?;
|
||||
args = vec![reg];
|
||||
}
|
||||
|
||||
// Special instructions
|
||||
Opcode::Int => {
|
||||
let val = expect_type!(self.next()?, Immediate)?;
|
||||
args = vec![val];
|
||||
}
|
||||
|
||||
// Instructions with no arguments
|
||||
Opcode::Hlt
|
||||
| Opcode::Nop
|
||||
| Opcode::Ret
|
||||
| Opcode::IRet
|
||||
| Opcode::Return
|
||||
| Opcode::Func => {
|
||||
args = vec![];
|
||||
}
|
||||
|
||||
Opcode::Data | Opcode::Segment => {
|
||||
return Err(AssembleError::Generic);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(node!(label, opcode, args: args))
|
||||
}
|
||||
|
||||
fn parse_data_definition(&mut self, opcode: Opcode) -> Result<Vec<Token>, AssembleError> {
|
||||
let mut values = Vec::new();
|
||||
|
||||
let name = expect_type!(self.next()?, Symbol)?;
|
||||
values.push(name);
|
||||
|
||||
match opcode {
|
||||
Opcode::Db => {
|
||||
// db can take string literals or u8 immediates
|
||||
while !self.tokens.is_empty() {
|
||||
let token = self
|
||||
.tokens
|
||||
.last()
|
||||
.expect("Expected a token for data definition, but found none");
|
||||
|
||||
match token {
|
||||
Token::StringLit(_) => {
|
||||
values.push(
|
||||
self.tokens
|
||||
.pop()
|
||||
.expect("Expected a token for data definition, but found none"),
|
||||
);
|
||||
}
|
||||
Token::Immediate(val) if u8::try_from(*val).is_ok() => {
|
||||
values.push(
|
||||
self.tokens
|
||||
.pop()
|
||||
.expect("Expected a token for data definition, but found none"),
|
||||
);
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Opcode::Dh => {
|
||||
// dh can take u16 immediates
|
||||
while !self.tokens.is_empty() {
|
||||
let token = self
|
||||
.tokens
|
||||
.last()
|
||||
.expect("Expected a token for data definition, but found none");
|
||||
|
||||
match token {
|
||||
Token::StringLit(_) => {
|
||||
values.push(
|
||||
self.tokens
|
||||
.pop()
|
||||
.expect("Expected a token for data definition, but found none"),
|
||||
);
|
||||
}
|
||||
Token::Immediate(val) if u16::try_from(*val).is_ok() => {
|
||||
values.push(
|
||||
self.tokens
|
||||
.pop()
|
||||
.expect("Expected a token for data definition, but found none"),
|
||||
);
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Opcode::Dw => {
|
||||
// dw can take u32 immediates
|
||||
while !self.tokens.is_empty() {
|
||||
match self
|
||||
.tokens
|
||||
.last()
|
||||
.expect("Expected a token for data definition, but found none")
|
||||
{
|
||||
Token::StringLit(_) => {
|
||||
values.push(
|
||||
self.tokens
|
||||
.pop()
|
||||
.expect("Expected a token for data definition, but found none"),
|
||||
);
|
||||
}
|
||||
Token::Immediate(val) => {
|
||||
values.push(
|
||||
self.tokens
|
||||
.pop()
|
||||
.expect("Expected a token for data definition, but found none"),
|
||||
);
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Result<Token, AssembleError> {
|
||||
if self.tokens.is_empty() {
|
||||
Err(AssembleError::UnexpectedEof)
|
||||
} else {
|
||||
Ok(self
|
||||
.tokens
|
||||
.pop()
|
||||
.expect("tokens vector was unexpectedly empty in next()"))
|
||||
}
|
||||
}
|
||||
|
||||
fn peek_next(&self) -> Result<Token, AssembleError> {
|
||||
if self.tokens.is_empty() {
|
||||
Err(AssembleError::UnexpectedEof)
|
||||
} else {
|
||||
Ok(self
|
||||
.tokens
|
||||
.last()
|
||||
.expect("peek_next called on empty tokens vector")
|
||||
.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::assembler::model::{Module, Node, Opcode, Symbol, Token};
|
||||
use crate::assembler::{quick_hash, AssembleError};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SectionId {
|
||||
Data,
|
||||
Text,
|
||||
}
|
||||
|
||||
/// Splits a flat (possibly multi-module) node list into separate data and
|
||||
/// text streams. Segment/Include markers are dropped here — module
|
||||
/// disambiguation lives on `Symbol.module`, not node position, so we don't
|
||||
/// need them past this point.
|
||||
pub fn split_sections(nodes: Vec<Node>) -> (Vec<Node>, Vec<Node>) {
|
||||
let mut data = Vec::new();
|
||||
let mut text = Vec::new();
|
||||
|
||||
for node in nodes {
|
||||
match node.opcode() {
|
||||
Opcode::Data => data.push(node),
|
||||
Opcode::Segment | Opcode::Include => {}
|
||||
_ => text.push(node),
|
||||
}
|
||||
}
|
||||
|
||||
(data, text)
|
||||
}
|
||||
|
||||
pub fn generate_symbol_table(
|
||||
data_nodes: &[Node],
|
||||
text_nodes: &[Node],
|
||||
) -> HashMap<Symbol, (u32, SectionId)> {
|
||||
let mut table = HashMap::new();
|
||||
|
||||
for (i, node) in data_nodes.iter().enumerate() {
|
||||
if let Some(symbol) = node.label() {
|
||||
table.insert(symbol, (4 * i as u32, SectionId::Data));
|
||||
}
|
||||
}
|
||||
for (i, node) in text_nodes.iter().enumerate() {
|
||||
if let Some(symbol) = node.label() {
|
||||
table.insert(symbol, (4 * i as u32, SectionId::Text));
|
||||
}
|
||||
}
|
||||
|
||||
table
|
||||
}
|
||||
|
||||
/// v1 loader convention: TEXT is placed at address 0, DATA immediately
|
||||
/// follows it. This is a stopgap until real relocations exist — it's the
|
||||
/// same assumption your current jmp-hack already makes, just explicit now.
|
||||
pub fn resolve_address((offset, section): (u32, SectionId), text_len_words: u32) -> u32 {
|
||||
match section {
|
||||
SectionId::Text => offset,
|
||||
SectionId::Data => (text_len_words * 4) + offset,
|
||||
}
|
||||
}
|
||||
|
||||
/// Patches every symbol reference in `text_nodes` with its resolved absolute
|
||||
/// address, and returns the symbol table so the caller can build the
|
||||
/// executable's symbol/string tables from it.
|
||||
pub fn resolve_symbols(
|
||||
data_nodes: &[Node],
|
||||
text_nodes: &mut [Node],
|
||||
) -> Result<HashMap<Symbol, (u32, SectionId)>, AssembleError> {
|
||||
let symbol_table = generate_symbol_table(data_nodes, text_nodes);
|
||||
let text_len = text_nodes.len() as u32;
|
||||
|
||||
for node in text_nodes.iter_mut() {
|
||||
match node.opcode() {
|
||||
Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc | Opcode::Lli | Opcode::Lui => {
|
||||
if let Token::Symbol(symbol) = node
|
||||
.arg(0)
|
||||
.expect("Expected argument 0 for jump-like opcode")
|
||||
{
|
||||
let Some(entry) = symbol_table.get(&symbol) else {
|
||||
return Err(AssembleError::UndefinedSymbol(symbol));
|
||||
};
|
||||
node.tokens[0] = Token::Immediate(resolve_address(*entry, text_len));
|
||||
}
|
||||
}
|
||||
Opcode::Jez | Opcode::Jnz => {
|
||||
if let Token::Symbol(symbol) = node
|
||||
.arg(1)
|
||||
.expect("Expected argument 1 for jump-like opcode")
|
||||
{
|
||||
let Some(entry) = symbol_table.get(&symbol) else {
|
||||
return Err(AssembleError::UndefinedSymbol(symbol));
|
||||
};
|
||||
node.tokens[1] = Token::Immediate(resolve_address(*entry, text_len));
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(symbol_table)
|
||||
}
|
||||
|
||||
// resolve_dependencies is unchanged — keep exactly as you have it.
|
||||
pub fn resolve_dependencies(
|
||||
mut nodes: Vec<Node>,
|
||||
base_dir: &Path,
|
||||
) -> Result<Vec<Node>, AssembleError> {
|
||||
// First we get a list of imports.
|
||||
let mut dependencies = Vec::new();
|
||||
for node in &nodes {
|
||||
if node.opcode() == Opcode::Include {
|
||||
// we want the path, and the name
|
||||
let name = if let Token::Symbol(name) = node
|
||||
.arg(0)
|
||||
.expect("Expected argument #0 for Include directive.")
|
||||
{
|
||||
name.name.clone()
|
||||
} else {
|
||||
unreachable!()
|
||||
}; //node.2.get(0).unwrap()
|
||||
|
||||
let Ok(Token::StringLit(path)) = node.arg(1) else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let full_path = base_dir.join(path);
|
||||
let canonical_path = full_path
|
||||
.canonicalize()
|
||||
.map_err(|_| AssembleError::InvalidFile(full_path.clone()))?;
|
||||
|
||||
let hash = quick_hash(&canonical_path);
|
||||
|
||||
dependencies.push((name, hash));
|
||||
}
|
||||
}
|
||||
|
||||
let mut changes = Vec::<(u32, u32, Symbol)>::new();
|
||||
// now we resolve the symbols on all the nodes
|
||||
// we need to check all operands for unresolved signals
|
||||
for (i, node) in nodes.clone().iter().enumerate() {
|
||||
let Node {
|
||||
tokens: operands, ..
|
||||
} = node;
|
||||
for (j, token) in operands.iter().enumerate() {
|
||||
if let Token::Symbol(symbol) = token {
|
||||
for d in &dependencies {
|
||||
if let Module::Unresolved(name) = symbol.module.clone() {
|
||||
if name != d.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let symbol = Symbol {
|
||||
name: symbol.name.clone(),
|
||||
module: Module::Resolved(d.1),
|
||||
};
|
||||
changes.push((i as u32, j as u32, symbol));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, j, symbol) in changes {
|
||||
nodes[i as usize].tokens[j as usize] = Token::Symbol(symbol);
|
||||
}
|
||||
|
||||
Ok(nodes)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct BuildContext {
|
||||
filename: PathBuf,
|
||||
stage: BuildStage,
|
||||
// Logging
|
||||
}
|
||||
|
||||
impl BuildContext {
|
||||
pub fn new(filename: PathBuf) -> Self {
|
||||
Self {
|
||||
filename,
|
||||
stage: BuildStage::Lexing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum BuildStage {
|
||||
Lexing,
|
||||
Parsing,
|
||||
PseudoExpansion,
|
||||
Linking,
|
||||
CodeGeneration,
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use super::Token;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AssembleError {
|
||||
UnexpectedToken { expected: String, got: Token },
|
||||
UnexpectedEof,
|
||||
}
|
||||
|
||||
impl AssembleError {
|
||||
pub fn unexpected_token(token: Token, expected: &str) -> Self {
|
||||
AssembleError::UnexpectedToken {
|
||||
expected: expected.to_string(),
|
||||
got: token,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
use std::{str::FromStr, thread, time::Duration};
|
||||
|
||||
use common::{asm::AsmOpcode, prelude::Register};
|
||||
|
||||
use crate::assembler::{AssembleError, Module, Symbol};
|
||||
use crate::assemblerv2::Token;
|
||||
|
||||
pub struct Lexer {
|
||||
src: Vec<u8>,
|
||||
module_id: u64,
|
||||
}
|
||||
|
||||
impl Lexer {
|
||||
pub fn new(src: String, module_id: u64) -> Self {
|
||||
Self {
|
||||
src: src.into_bytes().into_iter().rev().collect(),
|
||||
module_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<Vec<Token>, AssembleError> {
|
||||
let mut tokens = Vec::new();
|
||||
|
||||
while !self.src.is_empty() {
|
||||
// thread::sleep(Duration::from_millis(10));
|
||||
// println!("{}", *self.src.last().unwrap() as char);
|
||||
// println!("{:?}", tokens);
|
||||
|
||||
if self.src.ends_with(b"\n")
|
||||
|| self.src.ends_with(b"\r")
|
||||
|| self.src.ends_with(b" ")
|
||||
|| self.src.ends_with(b",")
|
||||
|| self.src.ends_with(b".")
|
||||
|| self.src.ends_with(b":")
|
||||
{
|
||||
self.src.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.src.ends_with(b"//") {
|
||||
self.src.pop();
|
||||
self.src.pop();
|
||||
self.parse_comment();
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.src.ends_with(b"*/") {
|
||||
self.src.pop();
|
||||
self.src.pop();
|
||||
self.parse_comment_multiline();
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.src.ends_with(b"\"") {
|
||||
self.src.pop();
|
||||
tokens.push(Token::StringLit(self.parse_string_literal()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.src.ends_with(b"'") {
|
||||
self.src.pop();
|
||||
tokens.push(Token::CharLit(self.parse_char_literal()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.src.last().unwrap().is_ascii_digit() {
|
||||
tokens.push(Token::Immediate(self.parse_number()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if matches!(self.src.last().unwrap(), b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_') {
|
||||
let mut buffer = String::new();
|
||||
|
||||
while matches!(self.src.last().unwrap(), b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')
|
||||
{
|
||||
buffer.push(self.src.pop().unwrap() as char);
|
||||
}
|
||||
|
||||
if let Ok(opcode) = AsmOpcode::from_str(&buffer) {
|
||||
tokens.push(Token::Opcode(opcode));
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(register) = Register::from_str(&buffer) {
|
||||
tokens.push(Token::Register(register));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for qualified symbol: identifier::identifier
|
||||
if self.src.ends_with(b"::") {
|
||||
self.src.pop();
|
||||
self.src.pop();
|
||||
let mut rhs = String::new();
|
||||
while matches!(
|
||||
self.src.last(),
|
||||
Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')
|
||||
) {
|
||||
rhs.push(self.src.pop().unwrap() as char);
|
||||
}
|
||||
if rhs.is_empty() {
|
||||
return Err(AssembleError::Generic);
|
||||
}
|
||||
tokens.push(Token::Symbol(Symbol {
|
||||
name: rhs,
|
||||
module: Module::Unresolved(buffer),
|
||||
}));
|
||||
continue;
|
||||
}
|
||||
|
||||
tokens.push(Token::Symbol(Symbol {
|
||||
name: buffer,
|
||||
module: Module::Resolved(self.module_id),
|
||||
}));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
fn parse_number(&mut self) -> u32 {
|
||||
match self.src.last_chunk::<2>().map(|[x, y]| [*y, *x]).as_ref() {
|
||||
Some(b"0x") => {
|
||||
self.src.pop();
|
||||
self.src.pop();
|
||||
let mut n = 0u32;
|
||||
while let Some(&b) = self.src.last() {
|
||||
if let Some(digit) = (b as char).to_digit(16) {
|
||||
self.src.pop();
|
||||
n = n * 16 + digit;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
n
|
||||
}
|
||||
Some(b"0b") => {
|
||||
self.src.pop();
|
||||
self.src.pop();
|
||||
let mut n = 0u32;
|
||||
while let Some(&b) = self.src.last() {
|
||||
if b == b'0' || b == b'1' {
|
||||
self.src.pop();
|
||||
n = n * 2 + (b - b'0') as u32;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
n
|
||||
}
|
||||
Some(b"0o") => {
|
||||
self.src.pop();
|
||||
self.src.pop();
|
||||
let mut n = 0u32;
|
||||
while let Some(&b) = self.src.last() {
|
||||
if matches!(b, b'0'..=b'7') {
|
||||
self.src.pop();
|
||||
n = n * 8 + (b - b'0') as u32;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
n
|
||||
}
|
||||
// decimal number
|
||||
_ if let Some(x) = self.src.last()
|
||||
&& x.is_ascii_digit() =>
|
||||
{
|
||||
let mut n = 0u32;
|
||||
while let Some(&b) = self.src.last() {
|
||||
if matches!(b, b'0'..=b'9') {
|
||||
self.src.pop();
|
||||
n = n * 10 + (b - b'0') as u32;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
n
|
||||
}
|
||||
_ => panic!("Invalid syntax"),
|
||||
}
|
||||
}
|
||||
|
||||
// returns nothing as the assembler discards comments
|
||||
fn parse_comment(&mut self) {
|
||||
while !self.src.is_empty() && !self.src.ends_with(b"\n") {
|
||||
self.src.pop();
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_comment_multiline(&mut self) {
|
||||
while !self.src.is_empty() && !self.src.ends_with(b"*/") {
|
||||
self.src.pop();
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_string_literal(&mut self) -> String {
|
||||
let mut result = String::new();
|
||||
while self.src.last().is_some() && !self.src.ends_with(b"\"") {
|
||||
let ch = self.src.pop().unwrap();
|
||||
if ch == b'\\' {
|
||||
let escaped = self
|
||||
.src
|
||||
.pop()
|
||||
.expect("A file should never end with a backslash!");
|
||||
result.push(match escaped {
|
||||
b'n' => '\n',
|
||||
b't' => '\t',
|
||||
b'r' => '\r',
|
||||
b'"' => '\"',
|
||||
b'\\' => '\\',
|
||||
_ => escaped as char,
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push(ch as char);
|
||||
}
|
||||
|
||||
self.src.pop().unwrap();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn parse_char_literal(&mut self) -> char {
|
||||
let ch = self
|
||||
.src
|
||||
.pop()
|
||||
.expect("Unexpected EOF while parsing char literal");
|
||||
|
||||
if ch == b'\\' {
|
||||
let escaped = self
|
||||
.src
|
||||
.pop()
|
||||
.expect("A file should never end with a backslash!");
|
||||
|
||||
assert!(
|
||||
self.src
|
||||
.pop()
|
||||
.expect("Unexpected EOF while parsing char literal")
|
||||
!= b'\'',
|
||||
"unterminated char literal"
|
||||
);
|
||||
|
||||
match escaped {
|
||||
b'n' => '\n',
|
||||
b't' => '\t',
|
||||
b'r' => '\r',
|
||||
b'"' => '\"',
|
||||
b'\\' => '\\',
|
||||
_ => escaped as char,
|
||||
}
|
||||
} else {
|
||||
assert!(
|
||||
self.src
|
||||
.pop()
|
||||
.expect("Unexpected EOF while parsing char literal")
|
||||
!= b'\'',
|
||||
"unterminated char literal"
|
||||
);
|
||||
ch as char
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
mod error;
|
||||
mod lexer;
|
||||
mod parser;
|
||||
|
||||
use core::fmt;
|
||||
use crate::assembler::{AssembleError, Symbol};
|
||||
use common::{asm::AsmOpcode, prelude::Register};
|
||||
use lexer::Lexer;
|
||||
use parser::Parser;
|
||||
|
||||
pub fn asm(input: &str) -> Result<Vec<u8>, AssembleError> {
|
||||
let mut lexer = Lexer::new(input.to_string(), 0);
|
||||
|
||||
let tokens = lexer.run()?;
|
||||
|
||||
let ast = Parser::new(tokens).parse().unwrap();
|
||||
|
||||
println!("{:#?}", ast);
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Token {
|
||||
Symbol(Symbol),
|
||||
Register(Register),
|
||||
Immediate(u32),
|
||||
StringLit(String),
|
||||
CharLit(char),
|
||||
Opcode(AsmOpcode),
|
||||
}
|
||||
|
||||
impl fmt::Display for Token {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Symbol(symbol) => write!(f, "{symbol}"),
|
||||
Self::Register(register) => write!(f, "{register}",),
|
||||
Self::Immediate(immediate) => write!(f, "{immediate}",),
|
||||
Self::StringLit(string_lit) => write!(f, "{string_lit}",),
|
||||
Self::CharLit(char_lit) => write!(f, "{char_lit}",),
|
||||
Self::Opcode(opcode) => write!(f, "{opcode}",),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use common::asm::{AsmInstruction, AsmOpcode};
|
||||
|
||||
use crate::assembler::{Symbol};
|
||||
|
||||
use crate::{expect_tt, expect_value};
|
||||
|
||||
use super::{error::AssembleError, Token};
|
||||
|
||||
pub struct AsmNode {
|
||||
pub label: Option<Symbol>,
|
||||
pub instruction: Option<AsmInstruction>,
|
||||
}
|
||||
|
||||
pub struct Parser {
|
||||
tokens: Vec<Token>,
|
||||
nodes: Option<Vec<AsmInstruction>>,
|
||||
dependencies: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new(tokens: Vec<Token>) -> Self {
|
||||
Parser {
|
||||
tokens,
|
||||
nodes: Some(vec![]),
|
||||
dependencies: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(&mut self) -> Result<Vec<AsmInstruction>, AssembleError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn parse_instruction(&mut self) -> Result<AsmNode, AssembleError> {
|
||||
let label = expect_value!(self.peek_next()?, Symbol).ok();
|
||||
if label.is_some() {
|
||||
self.next()?;
|
||||
}
|
||||
|
||||
let opcode = expect_value!(self.next()?, Opcode)?;
|
||||
|
||||
let ins = match opcode {
|
||||
AsmOpcode::Mov => {
|
||||
let src = expect_value!(self.next()?, Register)?;
|
||||
let dest = expect_value!(self.next()?, Register)?;
|
||||
AsmInstruction::Mov { src, dest }
|
||||
}
|
||||
AsmOpcode::CMov => {
|
||||
let src = expect_value!(self.next()?, Register)?;
|
||||
let dest = expect_value!(self.next()?, Register)?;
|
||||
let condition = expect_value!(self.next()?, Register)?;
|
||||
AsmInstruction::CMov {
|
||||
src,
|
||||
dest,
|
||||
condition,
|
||||
}
|
||||
}
|
||||
|
||||
// AsmOpcode::Ldb => {
|
||||
// let src = expect_value!(self.next()?, Register)?;
|
||||
// let dest = expect_value!(self.next()?, Register)?;
|
||||
// AsmInstruction::Ldb { src, dest }
|
||||
// }
|
||||
_ => {
|
||||
todo!()
|
||||
}
|
||||
};
|
||||
|
||||
Ok(AsmNode {
|
||||
label,
|
||||
instruction: Some(ins),
|
||||
})
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Result<Token, AssembleError> {
|
||||
if self.tokens.is_empty() {
|
||||
Err(AssembleError::UnexpectedEof)
|
||||
} else {
|
||||
Ok(self
|
||||
.tokens
|
||||
.pop()
|
||||
.expect("tokens vector was unexpectedly empty in next()"))
|
||||
}
|
||||
}
|
||||
|
||||
fn peek_next(&self) -> Result<Token, AssembleError> {
|
||||
if self.tokens.is_empty() {
|
||||
Err(AssembleError::UnexpectedEof)
|
||||
} else {
|
||||
Ok(self
|
||||
.tokens
|
||||
.last()
|
||||
.expect("peek_next called on empty tokens vector")
|
||||
.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! expect_tt {
|
||||
($token:expr, $($variant:ident),+) => {{
|
||||
let tok = $token;
|
||||
let tt = token.tt().to_string();
|
||||
|
||||
match tt.as_str() {
|
||||
$(
|
||||
stringify!($variant) => Ok(token),
|
||||
)+
|
||||
_ => {
|
||||
// let expected = format!("[{}]", vec![$(stringify!($variant)),+].join(" | "));
|
||||
Err(AssembleError::unexpected_token(
|
||||
tok,
|
||||
format!("[{}]", vec![$(stringify!($variant)),+].join(" | ")).as_str())
|
||||
)
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! expect_value {
|
||||
($token:expr, $variant:ident) => {{
|
||||
let tok = $token;
|
||||
match tok.clone() {
|
||||
Token::$variant(first, ..) => Ok(first),
|
||||
_ => Err(AssembleError::unexpected_token(tok, stringify!($variant))),
|
||||
}
|
||||
}};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#![deny(
|
||||
clippy::unwrap_used,
|
||||
clippy::nursery,
|
||||
clippy::perf,
|
||||
clippy::pedantic,
|
||||
clippy::complexity
|
||||
)]
|
||||
#![allow(
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::match_wildcard_for_single_variants
|
||||
)]
|
||||
|
||||
pub mod assembler;
|
||||
pub mod assemblerv2;
|
||||
// mod util;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::assembler::Assembler;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use assembler::prelude::*;
|
||||
use clap::{Parser, arg};
|
||||
use std::{fs, path::PathBuf};
|
||||
use std::io::Cursor;
|
||||
use assembler::assembler::AssembleError;
|
||||
use common::process::TraceableProcess;
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Args {
|
||||
#[arg(short = 'i')]
|
||||
pub input_path: PathBuf,
|
||||
#[arg(short = 'o')]
|
||||
pub output_path: PathBuf,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Parse command line arguments
|
||||
let args = Args::parse();
|
||||
|
||||
let assembler = Assembler::new(PathBuf::from(args.input_path));
|
||||
let result = assembler.run_sync().expect("assembler failed.");
|
||||
let mut buffer = Cursor::new(Vec::new());
|
||||
result.write(&mut buffer).unwrap();
|
||||
|
||||
if let Err(e) = fs::write(args.output_path, buffer.into_inner()) {
|
||||
eprintln!("Failed to write to output file: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "compiler"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,14 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "disassembler"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "dsa-d"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "disassembler"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.6.0", features = ["derive"] }
|
||||
common = { path = "../../libraries/common" }
|
||||
dsa_formats = { path = "../../libraries/dsa_formats" }
|
||||
@@ -0,0 +1,350 @@
|
||||
use common::isa::instructions::Opcode;
|
||||
use common::prelude::{Instruction, Register};
|
||||
use dsa_formats::binary_dse::DseExecutable;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Disassembly {
|
||||
pub symbols: Vec<SymbolLine>,
|
||||
pub text_lines: Vec<String>,
|
||||
pub data_lines: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SymbolLine {
|
||||
pub name: String,
|
||||
pub address: u32,
|
||||
pub section: SectionKind,
|
||||
pub binding: BindingKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SectionKind { Data, Text, Abs, Undef }
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BindingKind { Local, Global, Weak }
|
||||
|
||||
impl SectionKind {
|
||||
fn from_raw(v: u8) -> Self {
|
||||
match v { 1 => Self::Data, 2 => Self::Text, 0 => Self::Abs, _ => Self::Undef }
|
||||
}
|
||||
}
|
||||
impl BindingKind {
|
||||
fn from_raw(v: u8) -> Self {
|
||||
match v { 1 => Self::Global, 2 => Self::Weak, _ => Self::Local }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disassemble(exe: &DseExecutable) -> Disassembly {
|
||||
let symbols = build_symbol_lines(exe);
|
||||
|
||||
let mut text_labels: HashMap<u32, String> = HashMap::new();
|
||||
let mut data_labels: HashMap<u32, String> = HashMap::new();
|
||||
for sym in &symbols {
|
||||
match sym.section {
|
||||
SectionKind::Text => { text_labels.insert(sym.address, sym.name.clone()); }
|
||||
SectionKind::Data => { data_labels.insert(sym.address, sym.name.clone()); }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// symbols can refer to either section by full resolved address (per your
|
||||
// v1 loader convention: data lives right after text), so a single combined
|
||||
// map is actually what address-resolution needs — keep both, but merge
|
||||
// for lookups against full 32-bit addresses.
|
||||
let mut all_labels = text_labels.clone();
|
||||
all_labels.extend(data_labels.clone());
|
||||
|
||||
let text_lines = build_text_lines(&exe.text, &all_labels);
|
||||
let data_lines = build_data_lines(&exe.data, &data_labels);
|
||||
|
||||
Disassembly { symbols, text_lines, data_lines }
|
||||
}
|
||||
|
||||
fn build_symbol_lines(exe: &DseExecutable) -> Vec<SymbolLine> {
|
||||
exe.symbols
|
||||
.iter()
|
||||
.map(|s| SymbolLine {
|
||||
name: read_string(&exe.string_table, s.name_offset),
|
||||
address: s.value,
|
||||
section: SectionKind::from_raw(s.section),
|
||||
binding: BindingKind::from_raw(s.binding),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn read_string(strtab: &[u8], offset: u32) -> String {
|
||||
let start = offset as usize;
|
||||
let end = strtab[start..].iter().position(|&b| b == 0).map(|p| start + p).unwrap_or(strtab.len());
|
||||
String::from_utf8_lossy(&strtab[start..end]).into_owned()
|
||||
}
|
||||
|
||||
/// Address a jump-like instruction actually targets, if statically known.
|
||||
/// Returns None when the jump is register-relative (addr reg != Zero) since
|
||||
/// that depends on runtime state and can't be resolved here.
|
||||
fn static_jump_target(addr_reg: Register, imm: u16) -> Option<u32> {
|
||||
(addr_reg == Register::Zero).then_some(imm as u32)
|
||||
}
|
||||
|
||||
fn build_text_lines(words: &[u32], labels: &HashMap<u32, String>) -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
let mut i = 0;
|
||||
|
||||
while i < words.len() {
|
||||
let addr = 4 * i as u32;
|
||||
if let Some(label) = labels.get(&addr) {
|
||||
out.push(format!("{label}:"));
|
||||
}
|
||||
|
||||
let instr = Instruction(words[i]);
|
||||
let opcode = Opcode::from_u8(instr.opcode());
|
||||
|
||||
// Collapse Lli+Lui address-load pairs back into `lwi` (or a full
|
||||
// ldx/stx pseudo-op if a matching mem instruction follows), mirroring
|
||||
// what expand_ldx/expand_stx/expand_lwi originally expanded from.
|
||||
if let Some(Opcode::Lli) = opcode {
|
||||
if let Some(collapsed) = try_collapse_addr_load(words, i, labels) {
|
||||
out.push(format!(" {}", collapsed.text));
|
||||
i += collapsed.consumed;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
out.push(format!(" {}", render_instruction(instr, opcode, labels)));
|
||||
i += 1;
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
struct Collapsed {
|
||||
text: String,
|
||||
consumed: usize,
|
||||
}
|
||||
|
||||
fn try_collapse_addr_load(
|
||||
words: &[u32],
|
||||
i: usize,
|
||||
labels: &HashMap<u32, String>,
|
||||
) -> Option<Collapsed> {
|
||||
let lli = Instruction(words[i]);
|
||||
let lui = Instruction(*words.get(i + 1)?);
|
||||
|
||||
if Opcode::from_u8(lui.opcode())? as u8 != Opcode::Lui as u8 {
|
||||
return None;
|
||||
}
|
||||
if lui.dest_checked() != lli.dest_checked() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let dest = lli.dest_checked();
|
||||
let full_addr = (lli.imm16() as u32) | ((lui.imm16() as u32) << 16);
|
||||
let addr_text = labels.get(&full_addr).cloned().unwrap_or_else(|| format!("{full_addr:#010x}"));
|
||||
|
||||
// Check whether a mem op immediately follows, referencing `dest` —
|
||||
// that's the ldb/ldh/ldw/stb/sth/stw expansion pattern, not a bare lwi.
|
||||
if let Some(&mem_word) = words.get(i + 2) {
|
||||
let mem = Instruction(mem_word);
|
||||
if let Some(mem_op) = Opcode::from_u8(mem.opcode()) {
|
||||
let is_mem_op = matches!(
|
||||
mem_op,
|
||||
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw
|
||||
| Opcode::Stb | Opcode::Sth | Opcode::Stw
|
||||
);
|
||||
if is_mem_op && (mem.src1_checked() == dest || mem.dest_checked() == dest) {
|
||||
let mnemonic = format!("{mem_op:?}").to_lowercase();
|
||||
let other = if mem.src1_checked() == dest { mem.dest_checked() } else { mem.src1_checked() };
|
||||
return Some(Collapsed {
|
||||
text: format!("{mnemonic} {addr_text}, {other:?}, {:#06x}", mem.imm16()),
|
||||
consumed: 3,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(Collapsed {
|
||||
text: format!("lwi {addr_text}, {dest:?}"),
|
||||
consumed: 2,
|
||||
})
|
||||
}
|
||||
|
||||
fn render_instruction(instr: Instruction, opcode: Option<Opcode>, labels: &HashMap<u32, String>) -> String {
|
||||
let Some(op) = opcode else {
|
||||
return format!("; INVALID OPCODE {:#04x} (word {:#010x})", instr.opcode(), instr.0);
|
||||
};
|
||||
|
||||
let mnemonic = format!("{op:?}").to_lowercase();
|
||||
|
||||
match op {
|
||||
Opcode::Nop | Opcode::Hlt | Opcode::Ret | Opcode::IRet => mnemonic,
|
||||
|
||||
Opcode::Push => format!("{mnemonic} {:?}", instr.src1_checked()),
|
||||
Opcode::Pop => format!("{mnemonic} {:?}", instr.dest_checked()),
|
||||
|
||||
Opcode::Lli | Opcode::Lui => {
|
||||
format!("{mnemonic} {:?}, {:#06x}", instr.dest_checked(), instr.imm16())
|
||||
}
|
||||
|
||||
Opcode::Int => format!("{mnemonic} {:#06x}", instr.imm16()),
|
||||
|
||||
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw
|
||||
| Opcode::Stb | Opcode::Sth | Opcode::Stw | Opcode::Addi | Opcode::Subi => format!(
|
||||
"{mnemonic} {:?}, {:?}, {:#06x}",
|
||||
instr.src1_checked(), instr.dest_checked(), instr.imm16()
|
||||
),
|
||||
|
||||
Opcode::Jez | Opcode::Jnz => {
|
||||
let addr_reg = instr.dest_checked();
|
||||
let target = static_jump_target(addr_reg, instr.imm16())
|
||||
.and_then(|a| labels.get(&a).cloned());
|
||||
match target {
|
||||
Some(label) => format!("{mnemonic} {:?}, {label}", instr.src1_checked()),
|
||||
None if addr_reg == Register::Zero => {
|
||||
format!("{mnemonic} {:?}, {:#06x}", instr.src1_checked(), instr.imm16())
|
||||
}
|
||||
None => format!(
|
||||
"{mnemonic} {:?}, {:#06x}, {addr_reg:?}",
|
||||
instr.src1_checked(), instr.imm16()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc => {
|
||||
let addr_reg = instr.dest_checked();
|
||||
let target = static_jump_target(addr_reg, instr.imm16())
|
||||
.and_then(|a| labels.get(&a).cloned());
|
||||
match target {
|
||||
Some(label) => format!("{mnemonic} {label}"),
|
||||
None if addr_reg == Register::Zero => format!("{mnemonic} {:#06x}", instr.imm16()),
|
||||
None => format!("{mnemonic} {:#06x}, {addr_reg:?}", instr.imm16()),
|
||||
}
|
||||
}
|
||||
|
||||
Opcode::Mov | Opcode::Not => {
|
||||
format!("{mnemonic} {:?}, {:?}", instr.src1_checked(), instr.dest_checked())
|
||||
}
|
||||
|
||||
Opcode::CMov => format!(
|
||||
"{mnemonic} {:?}, {:?}, {:?}",
|
||||
instr.src1_checked(), instr.dest_checked(), instr.src2_checked()
|
||||
),
|
||||
|
||||
Opcode::Add | Opcode::Sub | Opcode::And | Opcode::Nand | Opcode::Or | Opcode::Nor
|
||||
| Opcode::Xor | Opcode::Xnor | Opcode::Ieq | Opcode::Ine | Opcode::Ilt | Opcode::Ile
|
||||
| Opcode::Igt | Opcode::Ige => format!(
|
||||
"{mnemonic} {:?}, {:?}, {:?}",
|
||||
instr.src1_checked(), instr.src2_checked(), instr.dest_checked()
|
||||
),
|
||||
|
||||
Opcode::Shl | Opcode::Shr => format!(
|
||||
"{mnemonic} {:?}, {:?}, {:?}, {}",
|
||||
instr.src1_checked(), instr.dest_checked(), instr.src2_checked(), instr.shamt()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_data_lines(data: &[u8], data_labels: &HashMap<u32, String>) -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
let mut i = 0;
|
||||
|
||||
while i < data.len() {
|
||||
let addr = i as u32;
|
||||
if let Some(label) = data_labels.get(&addr) {
|
||||
out.push(format!("{label}:"));
|
||||
}
|
||||
|
||||
// Try string detection only when this byte could actually start one.
|
||||
if data[i] != 0 {
|
||||
if let Some(end) = data[i..].iter().position(|&b| b == 0) {
|
||||
let slice = &data[i..i + end];
|
||||
if slice.iter().all(|&b| b.is_ascii_graphic() || b == b' ') {
|
||||
out.push(format!(" db \"{}\"", String::from_utf8_lossy(slice).escape_default()));
|
||||
i += end + 1; // skip the null terminator too
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not a string start: consume only the run of zero bytes here,
|
||||
// grouped into 4-byte words, stopping the instant a non-zero byte
|
||||
// appears so we never swallow the start of the next string/value.
|
||||
let run_end = data[i..]
|
||||
.iter()
|
||||
.position(|&b| b != 0)
|
||||
.map(|p| i + p)
|
||||
.unwrap_or(data.len());
|
||||
|
||||
if run_end == i {
|
||||
// data[i] is non-zero but didn't form a printable/terminated
|
||||
// string (e.g. binary constant) — dump one raw word.
|
||||
let mut buf = [0u8; 4];
|
||||
let take = (data.len() - i).min(4);
|
||||
buf[..take].copy_from_slice(&data[i..i + take]);
|
||||
out.push(format!(" dw {:#010x}", u32::from_le_bytes(buf)));
|
||||
i += take;
|
||||
} else {
|
||||
let mut j = i;
|
||||
while j < run_end {
|
||||
let take = (run_end - j).min(4);
|
||||
let mut buf = [0u8; 4];
|
||||
buf[..take].copy_from_slice(&data[j..j + take]);
|
||||
out.push(format!(" dw {:#010x}", u32::from_le_bytes(buf)));
|
||||
j += take;
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
impl fmt::Display for SectionKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Data => write!(f, "DATA"),
|
||||
Self::Text => write!(f, "TEXT"),
|
||||
Self::Abs => write!(f, "ABS"),
|
||||
Self::Undef => write!(f, "UNDEF"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BindingKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Local => write!(f, "local"),
|
||||
Self::Global => write!(f, "global"),
|
||||
Self::Weak => write!(f, "weak"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SymbolLine {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"; {:<24} {:#010x} {:<5} {}",
|
||||
self.name, self.address, self.section, self.binding
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Disassembly {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "; ==== symbols ====")?;
|
||||
for sym in &self.symbols {
|
||||
writeln!(f, "{sym}")?;
|
||||
}
|
||||
|
||||
writeln!(f, "\n; ==== data ====")?;
|
||||
for line in &self.data_lines {
|
||||
writeln!(f, "{line}")?;
|
||||
}
|
||||
|
||||
writeln!(f, "\n; ==== text ====")?;
|
||||
for line in &self.text_lines {
|
||||
writeln!(f, "{line}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
use std::fs;
|
||||
use clap::Parser;
|
||||
use disassembler::disassemble;
|
||||
use dsa_formats::binary_dse::DseExecutable;
|
||||
use std::fs::File;
|
||||
use std::io::{Cursor, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Args {
|
||||
#[arg(short = 'i')]
|
||||
pub input_path: PathBuf,
|
||||
#[arg(short = 'o')]
|
||||
pub output_path: PathBuf,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
let Ok(contents) = fs::read(args.input_path) else {
|
||||
panic!("Failed to read the input file")
|
||||
};
|
||||
|
||||
let Ok(exec) = DseExecutable::read(&mut Cursor::new(contents)) else {
|
||||
panic!("Failed to load the input file")
|
||||
};
|
||||
|
||||
let result = disassemble(&exec);
|
||||
let _bytes_written = File::create(args.output_path).expect("Failed to create the output file")
|
||||
.write(result.to_string().as_bytes()).expect("Failed to write to output file");
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "dsx_build"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,14 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
fn main() {
|
||||
println!("hello world!");
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "dsx_client"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "dsx_server"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "linker"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,14 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user