reoganised project
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "assembler"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "assembler"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "assembler"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
common = { path = "../dsa_common" }
|
||||
num_cpus = "1.17.0"
|
||||
threadpool = "1.8.1"
|
||||
Binary file not shown.
@@ -0,0 +1,265 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs,
|
||||
path::{self, Path, PathBuf},
|
||||
sync::{Arc, Mutex},
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
|
||||
use crate::assembler::{AssembleError, Token, expand_pseudo_ops, lexer, quick_hash};
|
||||
use crate::assembler::{Node, Parser, resolve_dependencies};
|
||||
// use crate::util::logging::Logger;
|
||||
|
||||
// pub fn new_assemble(path: &Path) {
|
||||
// let program = Program::new();
|
||||
// let program_ref = ProgramRef::new(program);
|
||||
|
||||
// let task = Module::build(path.to_path_buf(), program_ref.clone());
|
||||
// program_ref.add_task(task);
|
||||
|
||||
// // wait on all tasks to finish
|
||||
// for task in program_ref.get_tasks() {
|
||||
// let module = task.module.join().unwrap();
|
||||
// program_ref.add_module(module);
|
||||
// }
|
||||
// }
|
||||
|
||||
pub struct Program {
|
||||
pub main_path: PathBuf,
|
||||
registry: HashSet<u64>,
|
||||
modules: Vec<Module>,
|
||||
tasks: Vec<Task>,
|
||||
logger: Logger,
|
||||
}
|
||||
|
||||
impl Program {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
registry: HashSet::new(),
|
||||
modules: Vec::new(),
|
||||
tasks: Vec::new(),
|
||||
main_path: PathBuf::new(),
|
||||
logger: Logger::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_task(&mut self, task: Task) {
|
||||
self.tasks.push(task);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Program {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProgramRef {
|
||||
program: Arc<Mutex<Program>>,
|
||||
}
|
||||
|
||||
impl ProgramRef {
|
||||
#[must_use]
|
||||
pub fn new(program: Program) -> Self {
|
||||
Self {
|
||||
program: Arc::new(Mutex::new(program)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(&self, path: &Path) {
|
||||
self.program
|
||||
.lock()
|
||||
.expect("Failed to acquire program lock")
|
||||
.registry
|
||||
.insert(quick_hash(path));
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_registered(&self, path: &Path) -> bool {
|
||||
self.program
|
||||
.lock()
|
||||
.expect("Failed to acquire program lock")
|
||||
.registry
|
||||
.contains(&quick_hash(path))
|
||||
}
|
||||
|
||||
// pub fn get_tasks(&self) -> Vec<&Task> {
|
||||
// self.program.lock().unwrap().tasks.iter().collect()
|
||||
// }
|
||||
|
||||
pub fn add_task(&self, task: Task) {
|
||||
self.program
|
||||
.lock()
|
||||
.expect("Failed to acquire program lock")
|
||||
.add_task(task);
|
||||
}
|
||||
|
||||
pub fn add_module(&self, module: Module) {
|
||||
self.program
|
||||
.lock()
|
||||
.expect("Failed to acquire program lock")
|
||||
.modules
|
||||
.push(module);
|
||||
}
|
||||
|
||||
pub fn log(&self, message: &str) {
|
||||
self.program
|
||||
.lock()
|
||||
.expect("Failed to acquire program lock")
|
||||
.logger
|
||||
.log(message);
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ProgramRef {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
program: self.program.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Module {
|
||||
pub path: PathBuf,
|
||||
pub hash: u64,
|
||||
pub nodes: Vec<Node>,
|
||||
program: ProgramRef,
|
||||
}
|
||||
|
||||
impl Module {
|
||||
#[must_use]
|
||||
pub const fn new(
|
||||
path: PathBuf,
|
||||
hash: u64,
|
||||
nodes: Vec<Node>,
|
||||
program: ProgramRef,
|
||||
) -> Self {
|
||||
Self {
|
||||
path,
|
||||
hash,
|
||||
nodes,
|
||||
program,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(path: PathBuf, program: ProgramRef) -> Result<Task, AssembleError> {
|
||||
// Spawn a thread that creates the main function and executes the lexer and
|
||||
// parser.
|
||||
let handle = thread::spawn(move || {
|
||||
let mut module =
|
||||
Self::new(path.clone(), quick_hash(&path), Vec::new(), program.clone());
|
||||
|
||||
match module.lex() {
|
||||
Ok(tokens) => {
|
||||
module.parse(tokens);
|
||||
module.expand();
|
||||
module.prepare_dependencies();
|
||||
module
|
||||
}
|
||||
Err(why) => {
|
||||
eprintln!(
|
||||
"Error building program at path `{}`: {why}",
|
||||
path.display()
|
||||
);
|
||||
|
||||
// TODO: Find a way to make this work without panicking.
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Task { module: handle })
|
||||
}
|
||||
|
||||
fn lex(&self) -> Result<Vec<Token>, AssembleError> {
|
||||
if let Ok(path) = self.path.canonicalize() {
|
||||
self.program.log(&format!(
|
||||
"{:20} {:20} [{}]",
|
||||
"Building",
|
||||
self.get_filename(),
|
||||
path.display()
|
||||
));
|
||||
}
|
||||
|
||||
let src = fs::read_to_string(&self.path)
|
||||
.map_err(|_| AssembleError::InvalidFile(self.path.clone()))?;
|
||||
|
||||
let file_hash = quick_hash(&self.path);
|
||||
|
||||
self.program
|
||||
.log(&format!("{:20} {:20}", "Tokenising", self.get_filename()));
|
||||
|
||||
lexer::lexer(src, file_hash)
|
||||
}
|
||||
|
||||
fn parse(&mut self, tokens: Vec<Token>) -> Result<(), AssembleError> {
|
||||
self.program
|
||||
.log(&format!("{:20} {:20}", "Parsing", self.get_filename()));
|
||||
|
||||
let parsed = Parser::parse_nodes(tokens)?;
|
||||
self.nodes = parsed;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expand(&mut self) -> Result<(), AssembleError> {
|
||||
self.program
|
||||
.log(&format!("{:20} {:20}", "Expanding", self.get_filename()));
|
||||
|
||||
let expanded = expand_pseudo_ops(self.nodes.clone(), self.hash)?;
|
||||
self.nodes = expanded;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_dependencies(&self) -> Result<(), AssembleError> {
|
||||
let nodes = resolve_dependencies(
|
||||
self.nodes.clone(),
|
||||
self.path.parent().expect("File should have a parent path!"),
|
||||
)?;
|
||||
|
||||
let dependencies = Parser::get_dependencies(&nodes, &self.path)?;
|
||||
|
||||
for dep in dependencies {
|
||||
if self.program.is_registered(&dep) {
|
||||
// we have already built this module!
|
||||
continue;
|
||||
}
|
||||
|
||||
self.program.register(&dep);
|
||||
|
||||
// create new module
|
||||
// add the task to the program
|
||||
|
||||
match Self::build(dep, self.program.clone()) {
|
||||
Ok(task) => self.program.add_task(task),
|
||||
Err(why) => {
|
||||
eprintln!("Error building program: {why}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the filename from a [`PathBuf`].
|
||||
fn get_filename(&self) -> &str {
|
||||
self.path
|
||||
.file_name()
|
||||
.and_then(|f| f.to_str())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Gets the parent filepath from a [`PathBuf`].
|
||||
fn get_parent(&self) -> &str {
|
||||
self.path
|
||||
.parent()
|
||||
.and_then(|f| f.to_str())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Task {
|
||||
module: JoinHandle<Module>,
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
use common::{args, prelude::*};
|
||||
|
||||
use crate::assembler::model::{Node, Opcode};
|
||||
use crate::{assembler::AssembleError, expect_token};
|
||||
|
||||
fn log(message: &str) {
|
||||
println!("\x1b[32mINFO:\x1b[0m {message}");
|
||||
}
|
||||
|
||||
pub fn codegen(nodes: Vec<Node>) -> Result<Vec<Instruction>, AssembleError> {
|
||||
let mut instructions = vec![];
|
||||
|
||||
for node in nodes {
|
||||
instructions.push(build_instruction(&node)?);
|
||||
}
|
||||
|
||||
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 | Opcode::Movs => build_mov_instruction(opcode, &args),
|
||||
Opcode::Ldb
|
||||
| Opcode::Ldw
|
||||
| Opcode::Ldh
|
||||
| Opcode::Ldbs
|
||||
| Opcode::Ldhs
|
||||
| Opcode::Stb
|
||||
| Opcode::Stw
|
||||
| Opcode::Sth => build_memory_instruction(opcode, &args),
|
||||
Opcode::Lli | Opcode::Lui => build_load_immediate_instruction(opcode, &args),
|
||||
Opcode::Jmp
|
||||
| Opcode::Jeq
|
||||
| Opcode::Jne
|
||||
| Opcode::Jgt
|
||||
| Opcode::Jge
|
||||
| Opcode::Jlt
|
||||
| Opcode::Jle => build_jump_instruction(opcode, &args),
|
||||
Opcode::Cmp => build_compare_instruction(&args),
|
||||
Opcode::Inc | Opcode::Dec => build_inc_dec_instruction(opcode, &args),
|
||||
Opcode::Shl | Opcode::Shr => build_shift_instruction(opcode, &args),
|
||||
Opcode::Add
|
||||
| Opcode::Sub
|
||||
| Opcode::And
|
||||
| Opcode::Or
|
||||
| Opcode::Xor
|
||||
| Opcode::Nand
|
||||
| Opcode::Nor
|
||||
| Opcode::Xnor => build_arithmetic_instruction(opcode, &args),
|
||||
Opcode::AddI | Opcode::SubI => {
|
||||
build_arithmetic_immediate_instruction(opcode, &args)
|
||||
}
|
||||
Opcode::Not => build_not_instruction(&args),
|
||||
Opcode::Int => build_interrupt_instruction(&args),
|
||||
Opcode::Irt => Ok(Instruction::IntReturn),
|
||||
Opcode::Hlt => Ok(Instruction::Halt),
|
||||
Opcode::Data => build_data_instruction(&args),
|
||||
Opcode::Segment => build_segment_instruction(&args),
|
||||
// These pseudo-instructions should have been expanded!
|
||||
Opcode::Db
|
||||
| Opcode::Dh
|
||||
| Opcode::Dw
|
||||
| Opcode::Resb
|
||||
| Opcode::Resh
|
||||
| Opcode::Resw
|
||||
| Opcode::Push
|
||||
| Opcode::Pop
|
||||
| Opcode::Lwi
|
||||
| Opcode::Include
|
||||
| Opcode::Call
|
||||
| Opcode::Return
|
||||
| Opcode::Pusha
|
||||
| Opcode::Popa => Err(AssembleError::InvalidArg),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_mov_instruction(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::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(args!(R, sr1: src, dr: dest))),
|
||||
Opcode::Movs => Ok(Instruction::MovSigned(args!(R, sr1: src, dr: dest))),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_memory_instruction(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(base_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 base = expect_token!(base_token, Register)?;
|
||||
let dest = expect_token!(dest_token, Register)?;
|
||||
let offset = expect_token!(offset_token, Immediate)?;
|
||||
let instruction_args = args!(I, immediate: offset as u16, r1: base, r2: dest);
|
||||
|
||||
match opcode {
|
||||
Opcode::Ldb => Ok(Instruction::LoadByte(instruction_args)),
|
||||
Opcode::Ldw => Ok(Instruction::LoadWord(instruction_args)),
|
||||
Opcode::Ldh => Ok(Instruction::LoadHalfword(instruction_args)),
|
||||
Opcode::Ldbs => Ok(Instruction::LoadByteSigned(instruction_args)),
|
||||
Opcode::Ldhs => Ok(Instruction::LoadHalfwordSigned(instruction_args)),
|
||||
Opcode::Stb => Ok(Instruction::StoreByte(instruction_args)),
|
||||
Opcode::Stw => Ok(Instruction::StoreWord(instruction_args)),
|
||||
Opcode::Sth => Ok(Instruction::StoreHalfword(instruction_args)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_load_immediate_instruction(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::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 => {
|
||||
let instruction_args = args!(I, immediate: value as u16, r1: dest);
|
||||
Ok(Instruction::LoadLowerImmediate(instruction_args))
|
||||
}
|
||||
Opcode::Lui => {
|
||||
let upper_value = value >> 16;
|
||||
let instruction_args = args!(I, immediate: upper_value as u16, r1: dest);
|
||||
Ok(Instruction::LoadUpperImmediate(instruction_args))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_jump_instruction(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::Token],
|
||||
) -> Result<Instruction, AssembleError> {
|
||||
let Some(address_token) = args.first() else {
|
||||
return Err(AssembleError::MissingArgument(0));
|
||||
};
|
||||
let Some(offset_token) = args.get(1) else {
|
||||
return Err(AssembleError::MissingArgument(1));
|
||||
};
|
||||
|
||||
let address = expect_token!(address_token, Immediate)?;
|
||||
let offset = expect_token!(offset_token, Register)?;
|
||||
let instruction_args = args!(I, immediate: address as u16, r1: offset);
|
||||
|
||||
match opcode {
|
||||
Opcode::Jmp => Ok(Instruction::Jump(instruction_args)),
|
||||
Opcode::Jeq => Ok(Instruction::JumpEq(instruction_args)),
|
||||
Opcode::Jne => Ok(Instruction::JumpNeq(instruction_args)),
|
||||
Opcode::Jgt => Ok(Instruction::JumpGt(instruction_args)),
|
||||
Opcode::Jge => Ok(Instruction::JumpGe(instruction_args)),
|
||||
Opcode::Jlt => Ok(Instruction::JumpLt(instruction_args)),
|
||||
Opcode::Jle => Ok(Instruction::JumpLe(instruction_args)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_compare_instruction(
|
||||
args: &[crate::assembler::model::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 left = expect_token!(left_token, Register)?;
|
||||
let right = expect_token!(right_token, Register)?;
|
||||
Ok(Instruction::Compare(args!(R, sr1: left, sr2: right)))
|
||||
}
|
||||
|
||||
fn build_inc_dec_instruction(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::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::Inc => Ok(Instruction::Increment(args!(R, sr1: reg))),
|
||||
Opcode::Dec => Ok(Instruction::Decrement(args!(R, sr1: reg))),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_shift_instruction(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::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)?;
|
||||
|
||||
match opcode {
|
||||
Opcode::Shl => Ok(Instruction::ShiftLeft(
|
||||
args!(R, sr1: src, sr2: r_shamt, shamt: i_shamt, dr: dest),
|
||||
)),
|
||||
Opcode::Shr => Ok(Instruction::ShiftRight(
|
||||
args!(R, sr1: src, sr2: r_shamt, shamt: i_shamt, dr: dest),
|
||||
)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_arithmetic_instruction(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::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)?;
|
||||
let instruction_args = args!(R, sr1: left, sr2: right, dr: dest);
|
||||
|
||||
match opcode {
|
||||
Opcode::Add => Ok(Instruction::Add(instruction_args)),
|
||||
Opcode::Sub => Ok(Instruction::Sub(instruction_args)),
|
||||
Opcode::And => Ok(Instruction::And(instruction_args)),
|
||||
Opcode::Or => Ok(Instruction::Or(instruction_args)),
|
||||
Opcode::Xor => Ok(Instruction::Xor(instruction_args)),
|
||||
Opcode::Nand => Ok(Instruction::Nand(instruction_args)),
|
||||
Opcode::Nor => Ok(Instruction::Nor(instruction_args)),
|
||||
Opcode::Xnor => Ok(Instruction::Xnor(instruction_args)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_arithmetic_immediate_instruction(
|
||||
opcode: Opcode,
|
||||
args: &[crate::assembler::model::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)?;
|
||||
let instruction_args = args!(I, immediate: immediate, r1: reg, r2: dest);
|
||||
|
||||
match opcode {
|
||||
Opcode::AddI => Ok(Instruction::AddImmediate(instruction_args)),
|
||||
Opcode::SubI => Ok(Instruction::SubImmediate(instruction_args)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_not_instruction(
|
||||
args: &[crate::assembler::model::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 reg = expect_token!(reg_token, Register)?;
|
||||
let dest = expect_token!(dest_token, Register)?;
|
||||
Ok(Instruction::Not(args!(R, sr1: reg, dr: dest)))
|
||||
}
|
||||
|
||||
fn build_interrupt_instruction(
|
||||
args: &[crate::assembler::model::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::Interrupt(Interrupt::Software(code)))
|
||||
}
|
||||
|
||||
fn build_data_instruction(
|
||||
args: &[crate::assembler::model::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: &[crate::assembler::model::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::Segment(immediate))
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
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::Push => expand_push(&node, result)?,
|
||||
Opcode::Pop => expand_pop(&node, result)?,
|
||||
Opcode::Pusha => expand_pusha(&node, result)?,
|
||||
Opcode::Popa => expand_popa(&node, result)?,
|
||||
Opcode::Call => expand_call(&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(())
|
||||
}
|
||||
|
||||
fn expand_push(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||
let label = current.label();
|
||||
let Ok(arg0) = current.arg(0) else {
|
||||
return Err(AssembleError::Generic);
|
||||
};
|
||||
|
||||
let reg = expect_type!(arg0, Register)?;
|
||||
let spr = Token::Register(Register::Spr);
|
||||
|
||||
nodes.extend(vec![
|
||||
node!(label, Opcode::SubI, spr, 4, spr),
|
||||
node!(None, Opcode::Stw, reg, spr, 0),
|
||||
]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expand_pusha(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||
let label = current.label();
|
||||
let Ok(arg0) = current.arg(0) else {
|
||||
return Err(AssembleError::Generic);
|
||||
};
|
||||
|
||||
let count = expect_token!(arg0, Immediate)?;
|
||||
let spr = Token::Register(Register::Spr);
|
||||
let registers: Vec<Register> = Register::general();
|
||||
|
||||
nodes.push(node!(
|
||||
label,
|
||||
Opcode::SubI,
|
||||
spr,
|
||||
Token::Immediate(count * 4),
|
||||
spr
|
||||
));
|
||||
|
||||
nodes.extend((0..count).rev().map(|i| {
|
||||
node!(
|
||||
None,
|
||||
Opcode::Stw,
|
||||
Token::Register(registers[i as usize]),
|
||||
spr,
|
||||
Token::Immediate(i * 4)
|
||||
)
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expand_popa(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||
let label = current.label();
|
||||
|
||||
let Ok(arg0) = current.arg(0) else {
|
||||
return Err(AssembleError::Generic);
|
||||
};
|
||||
|
||||
let count = expect_token!(arg0, Immediate)?;
|
||||
let spr = Token::Register(Register::Spr);
|
||||
let registers: Vec<Register> = Register::general();
|
||||
|
||||
nodes.extend((0..count).rev().map(|i| {
|
||||
node!(
|
||||
{ if i == 0 { label.clone() } else { None } },
|
||||
Opcode::Ldw,
|
||||
spr,
|
||||
Token::Register(registers[i as usize]),
|
||||
Token::Immediate(i * 4)
|
||||
)
|
||||
}));
|
||||
|
||||
nodes.push(node!(
|
||||
None,
|
||||
Opcode::AddI,
|
||||
spr,
|
||||
Token::Immediate(count * 4),
|
||||
spr
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expand_call(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||
let label = current.label();
|
||||
|
||||
let Ok(arg0) = current.arg(0) else {
|
||||
return Err(AssembleError::Generic);
|
||||
};
|
||||
|
||||
let addr = expect_type!(arg0, Symbol)?;
|
||||
let spr = Token::Register(Register::Spr);
|
||||
let pcx = Token::Register(Register::Pcx);
|
||||
let zero = Token::Register(Register::Zero);
|
||||
|
||||
nodes.extend(vec![
|
||||
node!(label, Opcode::SubI, spr, 4, spr),
|
||||
node!(None, Opcode::Stw, pcx, spr, 0),
|
||||
node!(None, Opcode::Jmp, addr, zero),
|
||||
]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
nodes.extend(vec![
|
||||
node!(label, Opcode::Ldw, spr, ret, 0),
|
||||
node!(None, Opcode::AddI, spr, 4, spr),
|
||||
node!(None, Opcode::Jmp, 4, ret),
|
||||
]);
|
||||
}
|
||||
|
||||
fn expand_pop(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||
let label = current.label();
|
||||
|
||||
let Ok(arg0) = current.arg(0) else {
|
||||
return Err(AssembleError::Generic);
|
||||
};
|
||||
|
||||
let reg = expect_type!(arg0, Register)?;
|
||||
let spr = Token::Register(Register::Spr);
|
||||
|
||||
nodes.extend(vec![
|
||||
node!(label, Opcode::Ldw, spr, reg, 0),
|
||||
node!(None, Opcode::AddI, spr, 4, spr),
|
||||
]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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_be_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_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// println!("{:#?}", tokens);
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
pub fn parse_register(token: &str) -> Result<Option<Token>, AssembleError> {
|
||||
Ok(Register::try_from(token).map(Token::Register).ok())
|
||||
}
|
||||
|
||||
pub fn parse_opcode(token: &str) -> Result<Option<Token>, AssembleError> {
|
||||
if Opcode::OPCODES.contains(&token) {
|
||||
Ok(Some(Token::Opcode(Opcode::from_str(token).expect(
|
||||
"Opcode::from_str failed for a valid opcode token",
|
||||
))))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
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,288 @@
|
||||
#![allow(dead_code, unused)]
|
||||
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fmt, fs,
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
path::{Path, PathBuf},
|
||||
sync::{
|
||||
Arc, Mutex,
|
||||
mpsc::{self, Receiver, Sender},
|
||||
},
|
||||
thread,
|
||||
};
|
||||
|
||||
use common::{
|
||||
build::{BuildError, Builder},
|
||||
logging::{LogReceiver, Logger},
|
||||
prelude::Instruction,
|
||||
};
|
||||
|
||||
// Module declarations
|
||||
#[macro_use]
|
||||
pub mod macros;
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
// pub mod assembler;
|
||||
pub mod codegen;
|
||||
pub mod expand;
|
||||
pub mod lexer;
|
||||
pub mod model;
|
||||
pub mod parser;
|
||||
pub mod resolver;
|
||||
|
||||
// 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},
|
||||
};
|
||||
|
||||
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,
|
||||
|
||||
logs_rx: LogReceiver,
|
||||
}
|
||||
|
||||
impl From<AssembleError> for BuildError {
|
||||
fn from(err: AssembleError) -> Self {
|
||||
Self::Generic(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Builder for Assembler {
|
||||
type Output = Vec<u8>;
|
||||
|
||||
fn logs(&self) -> Vec<String> {
|
||||
self.logs_rx.logs()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
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,
|
||||
|
||||
// for logging
|
||||
logs_rx: LogReceiver::new(true),
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the compilation process in a separate thread
|
||||
fn start(&mut self) {
|
||||
if self.is_running {
|
||||
return;
|
||||
}
|
||||
|
||||
let src = self.src_path.clone();
|
||||
let tx = self.result_tx.clone();
|
||||
let logger = self.logs_rx.logger();
|
||||
|
||||
thread::spawn(move || {
|
||||
if let Ok(res) = assemble(&src, &logger) {
|
||||
let buffer: Vec<u8> = res
|
||||
.iter()
|
||||
.flat_map(|instruction| instruction.encode().to_be_bytes())
|
||||
.collect();
|
||||
tx.send(Ok(buffer))
|
||||
.expect("Failed to send compilation result from worker thread");
|
||||
}
|
||||
});
|
||||
|
||||
self.is_running = true;
|
||||
}
|
||||
|
||||
fn poll(&mut self) -> Option<Result<Self::Output, common::build::BuildError>> {
|
||||
if !self.is_running {
|
||||
return None;
|
||||
}
|
||||
|
||||
match self
|
||||
.result_rx
|
||||
.as_ref()
|
||||
.expect("result_rx should be Some while compilation is running")
|
||||
.try_recv()
|
||||
{
|
||||
Ok(result) => {
|
||||
self.is_running = false;
|
||||
Some(result.map_err(std::convert::Into::into))
|
||||
}
|
||||
Err(mpsc::TryRecvError::Empty) => None,
|
||||
Err(mpsc::TryRecvError::Disconnected) => {
|
||||
self.is_running = false;
|
||||
Some(Err(BuildError::Generic(String::from(
|
||||
"Compilation terminated before a result was returned",
|
||||
))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Block until compilation is complete and return the result
|
||||
fn output(&mut self) -> Result<Self::Output, common::build::BuildError> {
|
||||
if let Ok(result) = self
|
||||
.result_rx
|
||||
.take()
|
||||
.expect("result_rx should be Some while waiting for compilation result")
|
||||
.recv()
|
||||
{
|
||||
self.is_running = false;
|
||||
result.map_err(std::convert::Into::into)
|
||||
} else {
|
||||
self.is_running = false;
|
||||
Err(BuildError::Generic(String::from(
|
||||
"Compilation terminated before a result was returned",
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Assembler {}
|
||||
|
||||
fn assemble(src: &Path, logger: &Logger) -> Result<Vec<Instruction>, AssembleError> {
|
||||
let mut modules = HashSet::new();
|
||||
let mut program = Program::new();
|
||||
|
||||
let hash = quick_hash(src);
|
||||
|
||||
if modules.contains(&hash) {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
prepare_dependency(src, &mut modules, &mut program, &logger)?;
|
||||
|
||||
let mut nodes = program.nodes.clone();
|
||||
|
||||
create_sections(&mut nodes)?;
|
||||
resolve_symbols(&mut nodes)?;
|
||||
|
||||
logger.info("Generating assembly output...");
|
||||
|
||||
let instructions = codegen(nodes)?;
|
||||
|
||||
logger.info("Compilation Successful");
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
fn prepare_dependency(
|
||||
path: &Path,
|
||||
modules: &mut HashSet<u64>,
|
||||
program: &mut Program,
|
||||
logger: &Logger,
|
||||
) -> 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() {
|
||||
logger.info(&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);
|
||||
|
||||
logger.info(&format!("{:20} {:20}", "Tokenising", filename));
|
||||
let tokens = lexer::lexer(src, file_hash)?;
|
||||
|
||||
logger.info(&format!("{:20} {:20}", "Parsing", filename));
|
||||
let parsed = Parser::parse_nodes(tokens)?;
|
||||
|
||||
logger.info(&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)?;
|
||||
|
||||
logger.info(&format!("{:20} {:20}", "Expanding Pseudo-ops", filename));
|
||||
|
||||
// add a section instruction
|
||||
nodes.insert(
|
||||
0,
|
||||
node!(None, Opcode::Segment, Token::Immediate(file_hash as u32)),
|
||||
);
|
||||
|
||||
// for n in &nodes {
|
||||
// println!("{n}");
|
||||
// }
|
||||
|
||||
program.add_module(nodes);
|
||||
|
||||
for dep in deps {
|
||||
logger.info(&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, logger)?;
|
||||
}
|
||||
}
|
||||
|
||||
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 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,438 @@
|
||||
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::Movs => 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::Jeq => write!(f, "jeq"),
|
||||
Self::Jne => write!(f, "jne"),
|
||||
Self::Jgt => write!(f, "jgt"),
|
||||
Self::Jge => write!(f, "jge"),
|
||||
Self::Jlt => write!(f, "jlt"),
|
||||
Self::Jle => write!(f, "jle"),
|
||||
Self::Cmp => write!(f, "cmp"),
|
||||
Self::Inc => write!(f, "inc"),
|
||||
Self::Dec => write!(f, "dec"),
|
||||
Self::Shl => write!(f, "shl"),
|
||||
Self::Shr => write!(f, "shr"),
|
||||
Self::Add => write!(f, "add"),
|
||||
Self::Sub => write!(f, "sub"),
|
||||
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::Irt => write!(f, "irt"),
|
||||
Self::Hlt => write!(f, "hlt"),
|
||||
Self::AddI => write!(f, "addi"),
|
||||
Self::SubI => write!(f, "subi"),
|
||||
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::Call => write!(f, "call"),
|
||||
Self::Return => write!(f, "return"),
|
||||
Self::Pusha => write!(f, "pusha"),
|
||||
Self::Popa => write!(f, "popa"),
|
||||
|
||||
// meta instructions
|
||||
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),
|
||||
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::Opcode(opcode) => write!(f, "{opcode}",),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum TokenType {
|
||||
Symbol,
|
||||
Register,
|
||||
Immediate,
|
||||
StringLit,
|
||||
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::Opcode(_) => Self::Opcode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Opcode {
|
||||
// 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,
|
||||
Irt,
|
||||
Hlt,
|
||||
AddI,
|
||||
SubI,
|
||||
|
||||
// Pseudo-instructions
|
||||
Db,
|
||||
Dh,
|
||||
Dw,
|
||||
Resb,
|
||||
Resh,
|
||||
Resw,
|
||||
Push,
|
||||
Pop,
|
||||
Pusha,
|
||||
Popa,
|
||||
Lwi,
|
||||
Call,
|
||||
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),
|
||||
"movs" => Ok(Self::Movs),
|
||||
"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),
|
||||
"jmp" => Ok(Self::Jmp),
|
||||
"jeq" => Ok(Self::Jeq),
|
||||
"jne" => Ok(Self::Jne),
|
||||
"jgt" => Ok(Self::Jgt),
|
||||
"jge" => Ok(Self::Jge),
|
||||
"jlt" => Ok(Self::Jlt),
|
||||
"jle" => Ok(Self::Jle),
|
||||
"cmp" => Ok(Self::Cmp),
|
||||
"inc" => Ok(Self::Inc),
|
||||
"dec" => Ok(Self::Dec),
|
||||
"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),
|
||||
"int" => Ok(Self::Int),
|
||||
"irt" => Ok(Self::Irt),
|
||||
"hlt" => Ok(Self::Hlt),
|
||||
"addi" => Ok(Self::AddI),
|
||||
"subi" => Ok(Self::SubI),
|
||||
"db" => Ok(Self::Db),
|
||||
"dh" => Ok(Self::Dh),
|
||||
"dw" => Ok(Self::Dw),
|
||||
"resb" => Ok(Self::Resb),
|
||||
"resh" => Ok(Self::Resh),
|
||||
"resw" => Ok(Self::Resw),
|
||||
"push" => Ok(Self::Push),
|
||||
"pop" => Ok(Self::Pop),
|
||||
"lwi" => Ok(Self::Lwi),
|
||||
"include" => Ok(Self::Include),
|
||||
"call" => Ok(Self::Call),
|
||||
"return" => Ok(Self::Return),
|
||||
"pusha" => Ok(Self::Pusha),
|
||||
"popa" => Ok(Self::Popa),
|
||||
_ => 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", "irt", "hlt", "addi", "subi", // Pseudo-instructions
|
||||
"db", "dh", "dw", "resb", "resh", "resw", "push", "pop", "lwi", "call", "return",
|
||||
"pusha", "popa", // 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::Movs => 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::Jmp => Some(0x0D),
|
||||
Self::Jeq => Some(0x0E),
|
||||
Self::Jne => Some(0x0F),
|
||||
Self::Jgt => Some(0x10),
|
||||
Self::Jge => Some(0x11),
|
||||
Self::Jlt => Some(0x12),
|
||||
Self::Jle => Some(0x13),
|
||||
Self::Cmp => Some(0x14),
|
||||
Self::Inc => Some(0x15),
|
||||
Self::Dec => Some(0x16),
|
||||
Self::Shl => Some(0x17),
|
||||
Self::Shr => Some(0x18),
|
||||
Self::Add => Some(0x19),
|
||||
Self::Sub => Some(0x1A),
|
||||
Self::And => Some(0x1B),
|
||||
Self::Or => Some(0x1C),
|
||||
Self::Not => Some(0x1D),
|
||||
Self::Xor => Some(0x1E),
|
||||
Self::Nand => Some(0x1F),
|
||||
Self::Nor => Some(0x20),
|
||||
Self::Xnor => Some(0x21),
|
||||
Self::Int => Some(0x22),
|
||||
Self::Irt => Some(0x23),
|
||||
Self::Hlt => Some(0x24),
|
||||
Self::AddI => Some(0x25),
|
||||
Self::SubI => Some(0x26),
|
||||
Self::Segment => Some(0x27),
|
||||
|
||||
// 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::Push
|
||||
| Self::Pop
|
||||
| Self::Lwi
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
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::Movs => {
|
||||
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 | Opcode::Cmp => {
|
||||
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::Inc | Opcode::Dec => {
|
||||
let reg = expect_type!(self.next()?, Register, Symbol)?;
|
||||
args = vec![reg];
|
||||
}
|
||||
|
||||
Opcode::Include => {
|
||||
let mod_name = expect_type!(self.next()?, Symbol)?;
|
||||
let path = expect_type!(self.next()?, StringLit)?;
|
||||
args = vec![mod_name, path];
|
||||
}
|
||||
|
||||
// J-type instructions
|
||||
Opcode::Jmp
|
||||
| Opcode::Jeq
|
||||
| Opcode::Jne
|
||||
| Opcode::Jgt
|
||||
| Opcode::Jge
|
||||
| Opcode::Jlt
|
||||
| Opcode::Jle => {
|
||||
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::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];
|
||||
}
|
||||
|
||||
Opcode::Pusha | Opcode::Popa => {
|
||||
let count =
|
||||
expect_type!(self.next()?, Immediate).unwrap_or(Token::Immediate(8));
|
||||
args = vec![count];
|
||||
}
|
||||
|
||||
// Special instructions
|
||||
Opcode::Int => {
|
||||
let val = expect_type!(self.next()?, Immediate)?;
|
||||
args = vec![val];
|
||||
}
|
||||
|
||||
// Instructions with no arguments
|
||||
Opcode::Hlt | Opcode::Nop | Opcode::Irt | Opcode::Return => {
|
||||
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,153 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::canonicalize,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use common::prelude::Register;
|
||||
|
||||
use crate::assembler::model::{Module, Node, Opcode, Symbol, Token};
|
||||
use crate::assembler::quick_hash;
|
||||
use crate::{assembler::AssembleError, node};
|
||||
|
||||
pub fn resolve_symbols(nodes: &mut [Node]) -> Result<(), AssembleError> {
|
||||
let symbol_table = generate_symbol_table(nodes);
|
||||
|
||||
for node in nodes.iter_mut() {
|
||||
match node.opcode() {
|
||||
Opcode::Jmp
|
||||
| Opcode::Jeq
|
||||
| Opcode::Jne
|
||||
| Opcode::Jgt
|
||||
| Opcode::Jge
|
||||
| Opcode::Jlt
|
||||
| Opcode::Jle
|
||||
| Opcode::Lli
|
||||
| Opcode::Lui => {
|
||||
if let Token::Symbol(symbol) = node
|
||||
.arg(0)
|
||||
.expect("Expected argument 0 for jump-like opcode")
|
||||
{
|
||||
if let Some(address) = symbol_table.get(&symbol) {
|
||||
node.tokens[0] = Token::Immediate(*address);
|
||||
} else {
|
||||
return Err(AssembleError::UndefinedSymbol(symbol));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_symbol_table(nodes: &[Node]) -> HashMap<Symbol, u32> {
|
||||
let mut table = HashMap::new();
|
||||
|
||||
for (i, node) in nodes.iter().enumerate() {
|
||||
if let Some(symbol) = node.label() {
|
||||
table.insert(symbol, 4 * i as u32);
|
||||
}
|
||||
}
|
||||
|
||||
table
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn create_sections(nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||
let mut res = Vec::<Node>::with_capacity(nodes.len());
|
||||
|
||||
res.push(node!(None, Opcode::Segment, Token::Immediate(0)));
|
||||
|
||||
for n in nodes.iter() {
|
||||
if n.opcode() == Opcode::Data {
|
||||
res.push(n.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let start = res.len() + 1;
|
||||
res.insert(
|
||||
0,
|
||||
node!(
|
||||
None,
|
||||
Opcode::Jmp,
|
||||
Token::Immediate(start as u32 * 4),
|
||||
Token::Register(Register::Zero)
|
||||
),
|
||||
);
|
||||
for n in nodes.iter() {
|
||||
if !matches!(n.opcode(), Opcode::Data | Opcode::Include) {
|
||||
res.push(n.clone());
|
||||
}
|
||||
}
|
||||
|
||||
*nodes = res;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#![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 image_builder;
|
||||
pub mod tooling;
|
||||
mod util;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::assembler::Assembler;
|
||||
pub use crate::image_builder;
|
||||
pub use crate::tooling::brainf;
|
||||
pub use crate::tooling::project;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
use common::{self as _, build::Builder};
|
||||
|
||||
use assembler::{
|
||||
prelude::*,
|
||||
tooling::{brainf, project},
|
||||
};
|
||||
use std::{fs, io::Write, path::PathBuf};
|
||||
|
||||
fn main() {
|
||||
// Parse command line arguments
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
if args.len() == 2 && args[1] == "init" {
|
||||
project::tool_libcreate();
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
if args.len() == 2 && args[1] == "brainf" {
|
||||
let src = PathBuf::from("brainf.bf");
|
||||
let result = brainf::build(&src);
|
||||
|
||||
let mut file = match fs::File::create("brainf.dsb") {
|
||||
Err(e) => {
|
||||
eprintln!("Failed to create output file: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
Ok(file) => file,
|
||||
};
|
||||
|
||||
for instruction in result {
|
||||
if let Err(e) = file.write(&instruction.encode().to_be_bytes()) {
|
||||
eprintln!("Failed to write to output file: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
if args.len() != 5 || args[1] != "-i" || args[3] != "-o" {
|
||||
eprintln!("Usage: {} -i input_path -o output_path", args[0]);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let input_path = &args[2];
|
||||
let output_path = &args[4];
|
||||
|
||||
let mut engine = Assembler::new(PathBuf::from(input_path));
|
||||
engine.start();
|
||||
let result = engine.output().expect("assembler failed.");
|
||||
|
||||
if let Err(e) = fs::write(output_path, result) {
|
||||
eprintln!("Failed to write to output file: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use common::prelude::*;
|
||||
|
||||
use crate::{
|
||||
assembler::{
|
||||
Module, Node, Opcode, Symbol, Token, codegen, create_sections, expand_pseudo_ops,
|
||||
resolve_symbols,
|
||||
},
|
||||
node,
|
||||
};
|
||||
|
||||
#[must_use]
|
||||
pub fn build(src: &Path) -> Vec<Instruction> {
|
||||
let src = fs::read_to_string(src).expect("Failed to read source file");
|
||||
let mut nodes = parse(&src);
|
||||
|
||||
// we need to expand pseudoinstructions etc now
|
||||
nodes = expand_pseudo_ops(nodes, 0).expect("Failed to expand pseudo-operations");
|
||||
|
||||
create_sections(&mut nodes).expect("Failed to create sections");
|
||||
|
||||
for n in &nodes {
|
||||
println!("{n}");
|
||||
}
|
||||
|
||||
resolve_symbols(&mut nodes).expect("Failed to resolve symbols");
|
||||
|
||||
codegen(nodes).expect("Failed to generate code from nodes")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub fn parse(src: &str) -> Vec<Node> {
|
||||
let stack = Token::Immediate(0x10000);
|
||||
let acc = Token::Register(Register::Acc);
|
||||
let rga = Token::Register(Register::Rga);
|
||||
|
||||
let bpr = Token::Register(Register::Bpr);
|
||||
let spr = Token::Register(Register::Spr);
|
||||
let mut nodes = Vec::<Node>::new();
|
||||
|
||||
// Define symbols
|
||||
let print_start = Symbol {
|
||||
name: "print".to_string(),
|
||||
module: Module::Resolved(0),
|
||||
};
|
||||
|
||||
let tokens = lex(src);
|
||||
|
||||
// let _id = 0;
|
||||
let mut idstack = Vec::<u32>::new();
|
||||
|
||||
nodes.extend(vec![
|
||||
// set up a stack
|
||||
node!(None, Opcode::Lwi, stack, bpr),
|
||||
node!(None, Opcode::Mov, bpr, spr),
|
||||
// set up the data pointer
|
||||
node!(
|
||||
Some(Symbol {
|
||||
name: "main".to_string(),
|
||||
module: Module::Resolved(0)
|
||||
}),
|
||||
Opcode::Lwi,
|
||||
Token::Immediate(0x30000),
|
||||
rga
|
||||
),
|
||||
]);
|
||||
|
||||
for (id, tok) in tokens.iter().enumerate() {
|
||||
match tok {
|
||||
BfToken::Inc => {
|
||||
// inc acc
|
||||
nodes.extend(vec![node!(None, Opcode::Inc, acc)]);
|
||||
}
|
||||
BfToken::Dec => {
|
||||
// dec acc
|
||||
nodes.extend(vec![node!(None, Opcode::Dec, acc)]);
|
||||
}
|
||||
BfToken::IncPtr => {
|
||||
// stb acc, rga
|
||||
// add rga, 4
|
||||
// ldb rga, acc
|
||||
nodes.extend(vec![
|
||||
node!(None, Opcode::Stw, acc, rga, 0),
|
||||
node!(None, Opcode::AddI, rga, 4, rga),
|
||||
node!(None, Opcode::Ldw, rga, acc, 0),
|
||||
]);
|
||||
}
|
||||
BfToken::DecPtr => {
|
||||
// stb acc, rga
|
||||
// sub rga, 4
|
||||
// ldb rga, acc
|
||||
nodes.extend(vec![
|
||||
node!(None, Opcode::Stw, acc, rga, 0),
|
||||
node!(None, Opcode::SubI, rga, 4, rga),
|
||||
node!(None, Opcode::Ldw, rga, acc, 0),
|
||||
]);
|
||||
}
|
||||
BfToken::Out => {
|
||||
// push rga
|
||||
// call print
|
||||
// pop zero
|
||||
nodes.extend(vec![
|
||||
node!(None, Opcode::Push, acc),
|
||||
node!(None, Opcode::Call, Token::Symbol(print_start.clone())),
|
||||
node!(None, Opcode::Pop, Token::Register(Register::Zero)),
|
||||
]);
|
||||
}
|
||||
BfToken::In => {
|
||||
// Read a byte from input and store it at the current data pointer
|
||||
// Assuming we have an input function mapped to a specific memory location or I/O port
|
||||
nodes.extend(vec![
|
||||
// Read input (assuming input is mapped to memory address 0x40000)
|
||||
node!(None, Opcode::Ldw, Token::Immediate(0x40000), acc, 0),
|
||||
// Store the input byte at the current data pointer
|
||||
]);
|
||||
}
|
||||
BfToken::Forward => {
|
||||
// Start of loop [
|
||||
let loop_start = format!("loop_start_{id}");
|
||||
let loop_end = format!("loop_end_{id}");
|
||||
|
||||
// Push the current position for the matching ]
|
||||
idstack.push(id as u32);
|
||||
|
||||
// Load current cell value and check if zero
|
||||
nodes.extend(vec![
|
||||
// Compare with zero
|
||||
node!(None, Opcode::Cmp, acc, Token::Register(Register::Zero)),
|
||||
// If zero, jump to end of loop
|
||||
node!(
|
||||
None,
|
||||
Opcode::Jeq,
|
||||
Token::Symbol(Symbol {
|
||||
name: loop_end,
|
||||
module: Module::Resolved(0),
|
||||
}),
|
||||
Token::Register(Register::Zero)
|
||||
),
|
||||
]);
|
||||
|
||||
// Add label for loop start
|
||||
nodes.push(node!(
|
||||
Some(Symbol {
|
||||
name: loop_start,
|
||||
module: Module::Resolved(0),
|
||||
}),
|
||||
Opcode::Nop
|
||||
));
|
||||
}
|
||||
BfToken::Back => {
|
||||
// End of loop ]
|
||||
if let Some(start_id) = idstack.pop() {
|
||||
let loop_start = format!("loop_start_{start_id}");
|
||||
let loop_end = format!("loop_end_{start_id}");
|
||||
|
||||
// Jump back to the start of the loop
|
||||
nodes.extend(vec![
|
||||
// Compare with zero
|
||||
node!(None, Opcode::Cmp, acc, Token::Register(Register::Zero)),
|
||||
// If not zero, jump back to start of loop
|
||||
node!(
|
||||
None,
|
||||
Opcode::Jne,
|
||||
Token::Symbol(Symbol {
|
||||
name: loop_start,
|
||||
module: Module::Resolved(0),
|
||||
}),
|
||||
Token::Register(Register::Zero)
|
||||
),
|
||||
// Add label for loop end
|
||||
node!(
|
||||
Some(Symbol {
|
||||
name: loop_end,
|
||||
module: Module::Resolved(0),
|
||||
}),
|
||||
Opcode::Nop
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
// Unmatched closing bracket - could add error handling here
|
||||
eprintln!("Warning: Unmatched ']' at position {id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodes.push(node!(None, Opcode::Hlt));
|
||||
|
||||
insert_lib(&mut nodes);
|
||||
|
||||
nodes
|
||||
}
|
||||
|
||||
fn insert_lib(nodes: &mut Vec<Node>) {
|
||||
let bpr = Token::Register(Register::Bpr);
|
||||
let spr = Token::Register(Register::Spr);
|
||||
let rg0 = Token::Register(Register::Rg0);
|
||||
let rg1 = Token::Register(Register::Rg1);
|
||||
|
||||
let print_start = Symbol {
|
||||
name: "print".to_string(),
|
||||
module: Module::Resolved(0),
|
||||
};
|
||||
let current = Symbol {
|
||||
name: "current".to_string(),
|
||||
module: Module::Resolved(0),
|
||||
};
|
||||
// set up the program framework.
|
||||
nodes.extend(vec![
|
||||
// set display to 0x20000
|
||||
node!(
|
||||
None,
|
||||
Opcode::Dw,
|
||||
Token::Symbol(current.clone()),
|
||||
Token::Immediate(0x20000)
|
||||
),
|
||||
// print function
|
||||
// initialisation
|
||||
node!(Some(print_start), Opcode::Push, bpr),
|
||||
node!(None, Opcode::Mov, spr, bpr),
|
||||
// function body
|
||||
node!(None, Opcode::Ldw, bpr, rg0, Token::Immediate(8)),
|
||||
node!(
|
||||
None,
|
||||
Opcode::Ldw,
|
||||
Token::Symbol(current.clone()), // Load address of current
|
||||
rg1,
|
||||
Token::Immediate(0)
|
||||
),
|
||||
node!(None, Opcode::Stb, rg0, rg1, Token::Immediate(0)),
|
||||
node!(None, Opcode::AddI, rg1, Token::Immediate(1), rg1),
|
||||
// function return according to spec.
|
||||
node!(
|
||||
None,
|
||||
Opcode::Stw,
|
||||
rg1,
|
||||
Token::Symbol(current), // Store back to current
|
||||
Token::Immediate(0)
|
||||
),
|
||||
node!(None, Opcode::Mov, bpr, spr),
|
||||
node!(None, Opcode::Pop, bpr),
|
||||
node!(None, Opcode::Return),
|
||||
]);
|
||||
}
|
||||
|
||||
enum BfToken {
|
||||
Inc,
|
||||
Dec,
|
||||
IncPtr,
|
||||
DecPtr,
|
||||
Out,
|
||||
In,
|
||||
Forward,
|
||||
Back,
|
||||
}
|
||||
|
||||
fn lex(src: &str) -> Vec<BfToken> {
|
||||
src.chars()
|
||||
.filter_map(|c| match c {
|
||||
'+' => Some(BfToken::Inc),
|
||||
'-' => Some(BfToken::Dec),
|
||||
'>' => Some(BfToken::IncPtr),
|
||||
'<' => Some(BfToken::DecPtr),
|
||||
'.' => Some(BfToken::Out),
|
||||
',' => Some(BfToken::In),
|
||||
'[' => Some(BfToken::Forward),
|
||||
']' => Some(BfToken::Back),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn _create_symbol(id: u32) -> Symbol {
|
||||
Symbol {
|
||||
name: format!("label_{id}"),
|
||||
module: Module::Resolved(0),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod brainf;
|
||||
pub mod project;
|
||||
@@ -0,0 +1,93 @@
|
||||
use crate::util::input;
|
||||
|
||||
pub fn tool_libcreate() {
|
||||
let mut ptype: String;
|
||||
loop {
|
||||
ptype = input("Enter project type (bin|lib)");
|
||||
if ptype == "bin" || ptype == "lib" {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let project_name = input("Enter project name");
|
||||
let project_path = input("Enter Directory to create project in");
|
||||
|
||||
println!("[ Creating new {ptype} project {project_name} in {project_path} ]");
|
||||
|
||||
let template = match ptype.as_str() {
|
||||
"bin" => generate_bin_template(&project_name),
|
||||
"lib" => generate_lib_template(&project_name),
|
||||
_ => panic!("Invalid project type"),
|
||||
};
|
||||
|
||||
let path = format!("{project_path}/{project_name}.dsa");
|
||||
std::fs::write(path, template).expect("Unable to write file");
|
||||
}
|
||||
|
||||
fn generate_lib_template(module_name: &str) -> String {
|
||||
format!(
|
||||
r#"// {module_name}.dsa
|
||||
// usage:
|
||||
//
|
||||
// include {module_name} "<relative path>"
|
||||
//
|
||||
// usage for {module_name}_main:
|
||||
// push (arg1)
|
||||
// push (arg0)
|
||||
// call {module_name}::{module_name}_main
|
||||
// pop (arg0)
|
||||
// pop (arg1)
|
||||
|
||||
// Example data declarations
|
||||
// dw example_data: 0x0000
|
||||
|
||||
// Main function template
|
||||
{module_name}_main:
|
||||
// the correct way to start a function as defined by the calling convention
|
||||
push bpr
|
||||
mov spr, bpr
|
||||
|
||||
// explanation of how to access args
|
||||
ldw bpr, rg0, 8 // arg 0
|
||||
ldw bpr, rg0, 12 // arg 1
|
||||
|
||||
// your code goes here
|
||||
// Example: load example_data into rg1
|
||||
// ldw example_data, rg1
|
||||
|
||||
// the correct way to end a function as defined by the calling convention
|
||||
mov bpr, spr
|
||||
pop bpr
|
||||
return
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
fn generate_bin_template(project_name: &str) -> String {
|
||||
format!(
|
||||
r#"// {project_name}.dsa
|
||||
// Binary executable project
|
||||
|
||||
// Example Dependencies
|
||||
// include math "libs/math/math.dsa"
|
||||
include print "../resources/dsa/print.dsa"
|
||||
|
||||
// Data declarations - It is best practice to include these before any code!
|
||||
dw message: "Hello from {project_name}.dsa!" // strings are automatically null terminated!
|
||||
|
||||
// Program entry point - execution starts at the first non-definition line
|
||||
{project_name}:
|
||||
// Getting started: Calling external functions
|
||||
// Syntax: push (arg1), push (arg0), call namespace::function, pop (arg0), pop (arg1)
|
||||
|
||||
// Example: Print a string (if print library is included)
|
||||
ldw message, rg0 // load address of message
|
||||
push rg0 // push argument
|
||||
call print::print // call the print function
|
||||
pop rg0 // clean up stack
|
||||
|
||||
// Program must end with halt instruction
|
||||
halt
|
||||
"#,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused)]
|
||||
use std::{fmt, sync::mpsc::Sender};
|
||||
|
||||
// pub struct Entry {
|
||||
// etype: EntryType,
|
||||
// pub message: String,
|
||||
// }
|
||||
|
||||
// #[derive(Copy, Clone, Eq, PartialEq)]
|
||||
// enum EntryType {
|
||||
// Debug,
|
||||
// Info,
|
||||
// Warn,
|
||||
// Error,
|
||||
// Fatal,
|
||||
// }
|
||||
|
||||
// impl fmt::Display for EntryType {
|
||||
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// write!(
|
||||
// f,
|
||||
// "{:<5}",
|
||||
// match self {
|
||||
// Self::Debug => "DEBUG",
|
||||
// Self::Info => "INFO",
|
||||
// Self::Warn => "WARN",
|
||||
// Self::Error => "ERROR",
|
||||
// Self::Fatal => "FATAL",
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl fmt::Display for Entry {
|
||||
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// write!(f, "{}: {}", self.etype, self.message)
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,13 @@
|
||||
pub mod logging;
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
pub fn input(prompt: &str) -> String {
|
||||
print!("{prompt}\n > ");
|
||||
std::io::stdout().flush().expect("Failed to flush stdout");
|
||||
let mut input = String::new();
|
||||
std::io::stdin()
|
||||
.read_line(&mut input)
|
||||
.expect("Failed to read line from stdin");
|
||||
input.trim().to_string()
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "compiler"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.43"
|
||||
common = { path = "../dsa_common" }
|
||||
uuid = { version = "1.20.0", features = ["v4"] }
|
||||
@@ -0,0 +1,129 @@
|
||||
# This is a configuration file for the bacon tool
|
||||
#
|
||||
# Complete help on configuration: https://dystroy.org/bacon/config/
|
||||
#
|
||||
# You may check the current default at
|
||||
# https://github.com/Canop/bacon/blob/main/defaults/default-bacon.toml
|
||||
|
||||
default_job = "check"
|
||||
|
||||
[jobs.check]
|
||||
command = ["cargo", "check", "--color", "always"]
|
||||
need_stdout = false
|
||||
|
||||
[jobs.check-all]
|
||||
command = ["cargo", "check", "--all-targets", "--color", "always"]
|
||||
need_stdout = false
|
||||
|
||||
# Run clippy on the default target
|
||||
[jobs.clippy]
|
||||
command = [
|
||||
"cargo", "clippy",
|
||||
"--color", "always",
|
||||
]
|
||||
need_stdout = false
|
||||
|
||||
# Run clippy on all targets
|
||||
# To disable some lints, you may change the job this way:
|
||||
# [jobs.clippy-all]
|
||||
# command = [
|
||||
# "cargo", "clippy",
|
||||
# "--all-targets",
|
||||
# "--color", "always",
|
||||
# "--",
|
||||
# "-A", "clippy::bool_to_int_with_if",
|
||||
# "-A", "clippy::collapsible_if",
|
||||
# "-A", "clippy::derive_partial_eq_without_eq",
|
||||
# ]
|
||||
# need_stdout = false
|
||||
[jobs.clippy-all]
|
||||
command = [
|
||||
"cargo", "clippy",
|
||||
"--all-targets",
|
||||
"--color", "always",
|
||||
]
|
||||
need_stdout = false
|
||||
|
||||
# This job lets you run
|
||||
# - all tests: bacon test
|
||||
# - a specific test: bacon test -- config::test_default_files
|
||||
# - the tests of a package: bacon test -- -- -p config
|
||||
[jobs.test]
|
||||
command = [
|
||||
"cargo", "test", "--color", "always",
|
||||
"--", "--color", "always", # see https://github.com/Canop/bacon/issues/124
|
||||
]
|
||||
need_stdout = true
|
||||
|
||||
[jobs.nextest]
|
||||
command = [
|
||||
"cargo", "nextest", "run",
|
||||
"--color", "always",
|
||||
"--hide-progress-bar", "--failure-output", "final"
|
||||
]
|
||||
need_stdout = true
|
||||
analyzer = "nextest"
|
||||
|
||||
[jobs.doc]
|
||||
command = ["cargo", "doc", "--color", "always", "--no-deps"]
|
||||
need_stdout = false
|
||||
|
||||
# If the doc compiles, then it opens in your browser and bacon switches
|
||||
# to the previous job
|
||||
[jobs.doc-open]
|
||||
command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"]
|
||||
need_stdout = false
|
||||
on_success = "back" # so that we don't open the browser at each change
|
||||
|
||||
# You can run your application and have the result displayed in bacon,
|
||||
# if it makes sense for this crate.
|
||||
# Don't forget the `--color always` part or the errors won't be
|
||||
# properly parsed.
|
||||
[jobs.run]
|
||||
command = [
|
||||
"cargo", "run",
|
||||
"--color", "always",
|
||||
"--",
|
||||
"../resources/dsa/example.dsc",
|
||||
"../resources/dsa/example.dsa"
|
||||
# put launch parameters for your program behind a `--` separator
|
||||
]
|
||||
need_stdout = true
|
||||
allow_warnings = true
|
||||
background = true
|
||||
|
||||
# Run your long-running application (eg server) and have the result displayed in bacon.
|
||||
# For programs that never stop (eg a server), `background` is set to false
|
||||
# to have the cargo run output immediately displayed instead of waiting for
|
||||
# program's end.
|
||||
# 'on_change_strategy' is set to `kill_then_restart` to have your program restart
|
||||
# on every change (an alternative would be to use the 'F5' key manually in bacon).
|
||||
# If you often use this job, it makes sense to override the 'r' key by adding
|
||||
# a binding `r = job:run-long` at the end of this file .
|
||||
[jobs.run-long]
|
||||
command = [
|
||||
"cargo", "run",
|
||||
"--color", "always",
|
||||
# put launch parameters for your program behind a `--` separator
|
||||
]
|
||||
need_stdout = true
|
||||
allow_warnings = true
|
||||
background = false
|
||||
on_change_strategy = "kill_then_restart"
|
||||
|
||||
# This parameterized job runs the example of your choice, as soon
|
||||
# as the code compiles.
|
||||
# Call it as
|
||||
# bacon ex -- my-example
|
||||
[jobs.ex]
|
||||
command = ["cargo", "run", "--color", "always", "--example"]
|
||||
need_stdout = true
|
||||
allow_warnings = true
|
||||
|
||||
# You may define here keybindings that would be specific to
|
||||
# a project, for example a shortcut to launch a specific job.
|
||||
# Shortcuts to internal functions (scrolling, toggling, etc.)
|
||||
# should go in your personal global prefs.toml file instead.
|
||||
[keybindings]
|
||||
# alt-m = "job:my-job"
|
||||
c = "job:clippy-all" # comment this to have 'c' run clippy on only the default target
|
||||
@@ -0,0 +1,955 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use chrono::{DateTime, Local};
|
||||
|
||||
use super::registers::RegisterAllocator;
|
||||
use crate::backend::dsa::instruction::{InsBlock as IB, Instruction as I, Label};
|
||||
use crate::backend::dsa::registers::Register;
|
||||
|
||||
use crate::model::{
|
||||
AssignmentOperator, BinaryOperator, Call, CompilerError, ConstExpr, Declaration,
|
||||
Dependency, Expression, Number, Program, Statement, TypeId, UnaryOperator, Variable,
|
||||
};
|
||||
|
||||
pub struct CodeGenerator {
|
||||
ast: Program,
|
||||
imports: HashMap<String, I>,
|
||||
globals: HashMap<String, I>,
|
||||
functions: Vec<IB>,
|
||||
symbols: Vec<String>,
|
||||
allocator: RegisterAllocator,
|
||||
}
|
||||
|
||||
impl CodeGenerator {
|
||||
pub fn new(ast: Program) -> Self {
|
||||
CodeGenerator {
|
||||
ast,
|
||||
imports: HashMap::new(),
|
||||
globals: HashMap::new(),
|
||||
functions: Vec::new(),
|
||||
symbols: Vec::new(),
|
||||
allocator: RegisterAllocator::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn include(&mut self, name: impl Into<String>, path: impl Into<String>) {
|
||||
let name = name.into();
|
||||
self.imports.insert(name.clone(), I::include(name, path));
|
||||
}
|
||||
|
||||
fn is_global(&self, name: &str) -> bool {
|
||||
// Check if this variable is in the globals list
|
||||
self.globals.contains_key(name)
|
||||
}
|
||||
|
||||
pub fn generate(&mut self) -> Result<String, CompilerError> {
|
||||
// always include the print library for debugging!
|
||||
self.include("print", "./lib/io/print.dsa");
|
||||
|
||||
for block in self.ast.clone().declarations {
|
||||
match block {
|
||||
Declaration::Variable {
|
||||
var: Variable { name, .. },
|
||||
..
|
||||
} => self.symbols.push(name),
|
||||
Declaration::Function { name, .. } => self.symbols.push(name),
|
||||
Declaration::Dependency(Dependency { name, .. }) => {
|
||||
self.symbols.push(name)
|
||||
}
|
||||
Declaration::Struct { .. } => {} /* we can't do any code generation for
|
||||
* a struct yet. we may need to later
|
||||
* once these become class-like
|
||||
* objects with implementations */
|
||||
}
|
||||
}
|
||||
|
||||
for block in self.ast.clone().declarations {
|
||||
self.generate_block(block.clone())?;
|
||||
}
|
||||
|
||||
let assembly = self.generate_layout()?;
|
||||
Ok(assembly
|
||||
.iter()
|
||||
.map(|i| i.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"))
|
||||
}
|
||||
|
||||
fn generate_layout(&mut self) -> Result<IB, CompilerError> {
|
||||
let datetime: DateTime<Local> = SystemTime::now().into();
|
||||
|
||||
let mut block = IB::new();
|
||||
|
||||
block.extend(vec![
|
||||
I::global_comment(format!(
|
||||
"GENERATED BY DSC COMPILER
|
||||
Generated at {}",
|
||||
datetime.format("%Y-%m-%d %H:%M:%S")
|
||||
)),
|
||||
I::Newline,
|
||||
I::global_comment("Imports"),
|
||||
]);
|
||||
|
||||
block.extend(self.imports.values().cloned().collect::<Vec<_>>());
|
||||
|
||||
block.extend(vec![
|
||||
I::Newline,
|
||||
I::global_comment("Globals & Reserved Memory"),
|
||||
]);
|
||||
|
||||
block.extend(self.globals.values().cloned().collect::<Vec<_>>());
|
||||
|
||||
block.extend(vec![
|
||||
I::Newline,
|
||||
I::global_comment("Entry Point"),
|
||||
I::db_word("stack", 0x10000),
|
||||
I::db_string("message", "Process Exited with code:"),
|
||||
// init function for stack setup.
|
||||
I::label("_init"),
|
||||
I::ldw_label("stack", Register::Bpr),
|
||||
I::mov(Register::Bpr, Register::Spr),
|
||||
I::push(Register::Zero),
|
||||
I::call("main"),
|
||||
I::call("print::print_newline"),
|
||||
I::lwi_label("message", Register::Rg0),
|
||||
I::push(Register::Rg0),
|
||||
I::call("print::print"),
|
||||
I::pop(Register::Zero),
|
||||
I::call("print::print_hex_word"),
|
||||
I::pop(Register::Zero),
|
||||
I::Hlt,
|
||||
I::Newline,
|
||||
// default return block boilerplate
|
||||
I::global_comment("Return"),
|
||||
I::label("_ret"),
|
||||
I::mov(Register::Bpr, Register::Spr),
|
||||
I::pop(Register::Bpr),
|
||||
I::Return,
|
||||
]);
|
||||
|
||||
for function in self.functions.iter() {
|
||||
block.extend(function.iter().cloned());
|
||||
}
|
||||
|
||||
Ok(block)
|
||||
}
|
||||
|
||||
fn generate_global(&mut self, name: &str, init: Option<ConstExpr>) {
|
||||
let init = init.unwrap_or(ConstExpr::Number(0));
|
||||
match init {
|
||||
ConstExpr::Number(value) => {
|
||||
self.globals
|
||||
.insert(name.to_string(), I::db_word(name, value as u32));
|
||||
}
|
||||
ConstExpr::String(str) => {
|
||||
self.globals
|
||||
.insert(name.to_string(), I::db_string(name, str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_block(&mut self, block: Declaration) -> Result<(), CompilerError> {
|
||||
match block {
|
||||
Declaration::Variable { var, init, .. } => {
|
||||
self.generate_global(&var.name, init)
|
||||
}
|
||||
Declaration::Function {
|
||||
name,
|
||||
params,
|
||||
body,
|
||||
return_type,
|
||||
} => {
|
||||
let func = self.generate_function(&name, ¶ms, &body, return_type);
|
||||
self.functions.push(func);
|
||||
}
|
||||
Declaration::Dependency(Dependency { name, path }) => {
|
||||
self.include(name, path);
|
||||
}
|
||||
Declaration::Struct { .. } => {} /* can't do any codegen for these yet,
|
||||
* they're just types. */
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Example: Generate code for a function
|
||||
fn generate_function(
|
||||
&mut self,
|
||||
name: &str,
|
||||
params: &[Variable],
|
||||
body: &[Statement],
|
||||
return_type: TypeId,
|
||||
) -> IB {
|
||||
let mut code = IB::new();
|
||||
|
||||
// Reset allocator for new function
|
||||
self.allocator.reset();
|
||||
|
||||
let fmtparams = params
|
||||
.iter()
|
||||
.map(|p| format!("{}: {}", p.name, p.type_id))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
code.extend(vec![
|
||||
I::global_comment(format!("fn {name}({fmtparams}) -> {return_type}")),
|
||||
I::label(name),
|
||||
I::push(Register::Bpr),
|
||||
I::mov(Register::Spr, Register::Bpr),
|
||||
]);
|
||||
|
||||
// Allocate parameters to registers or stack locations
|
||||
for (i, param) in params.iter().enumerate() {
|
||||
let offset = 8 + (i as i32 * 4); // Parameters start at bpr+8
|
||||
// Track that this parameter is at a stack location
|
||||
let (reg, load_code) = self.allocator.alloc_var(¶m.name).unwrap();
|
||||
code.append(load_code);
|
||||
code.push(I::ldw_reg_offset(Register::Bpr, reg, offset));
|
||||
}
|
||||
|
||||
// Generate code for function body
|
||||
for stmt in body {
|
||||
let stmt_code = self.generate_statement(stmt, &mut code).unwrap();
|
||||
code.append(stmt_code);
|
||||
}
|
||||
|
||||
// automatically return at function end
|
||||
if let Some(x) = code.iter().last()
|
||||
&& let I::Jmp { target: Label(val) } = x
|
||||
&& val == "_ret"
|
||||
{
|
||||
} else {
|
||||
code.push(I::jmp("_ret"));
|
||||
}
|
||||
|
||||
code.insert(0, I::Newline);
|
||||
|
||||
code
|
||||
}
|
||||
|
||||
// Example: Generate code for a statement
|
||||
fn generate_statement(
|
||||
&mut self,
|
||||
stmt: &Statement,
|
||||
func_body: &mut IB,
|
||||
) -> Result<IB, CompilerError> {
|
||||
let mut code = IB::new();
|
||||
|
||||
match stmt {
|
||||
Statement::Declaration { var, value } => {
|
||||
if let Some(expr) = value {
|
||||
// Evaluate expression
|
||||
let (result_reg, expr_code) =
|
||||
self.generate_expression(expr, true, func_body)?;
|
||||
code.append(expr_code);
|
||||
|
||||
// Store result in variable
|
||||
let store_code = self.allocator.store_var(&var.name, &result_reg);
|
||||
code.append(store_code);
|
||||
|
||||
// Free temporary register
|
||||
self.allocator.free_temp(result_reg);
|
||||
} else {
|
||||
// Just declaring variable without initialization
|
||||
self.allocator.alloc_var(&var.name)?;
|
||||
}
|
||||
}
|
||||
|
||||
Statement::Break => unimplemented!("need scope tracking first!"),
|
||||
Statement::Continue => unimplemented!("need scope tracking first!"),
|
||||
Statement::Defer(_func) => unimplemented!("we need scope tracking first!"),
|
||||
|
||||
Statement::PtrWrite { ptr, value } => {
|
||||
let (result_reg, expr_code) =
|
||||
self.generate_expression(value, true, func_body)?;
|
||||
code.append(expr_code);
|
||||
|
||||
let (ptr_reg, ptr_code) =
|
||||
self.generate_expression(ptr, true, func_body)?;
|
||||
code.append(ptr_code);
|
||||
|
||||
code.push(I::stw_reg(result_reg, ptr_reg));
|
||||
|
||||
self.allocator.free_temp(result_reg);
|
||||
self.allocator.free_temp(ptr_reg);
|
||||
}
|
||||
|
||||
Statement::Assign {
|
||||
varname,
|
||||
value,
|
||||
operator,
|
||||
} => {
|
||||
// Evaluate expression
|
||||
let (result_reg, expr_code) =
|
||||
self.generate_expression(value, true, func_body)?;
|
||||
code.append(expr_code);
|
||||
|
||||
if *operator == AssignmentOperator::Assign {
|
||||
// Check if this is a global variable
|
||||
if self.is_global(varname) {
|
||||
// Store to global label
|
||||
code.push(I::stw_label(result_reg, varname.clone()))
|
||||
} else {
|
||||
// Store result in local variable
|
||||
let store_code = self.allocator.store_var(varname, &result_reg);
|
||||
code.append(store_code);
|
||||
}
|
||||
|
||||
// Free temporary register
|
||||
self.allocator.free_temp(result_reg);
|
||||
|
||||
return Ok(code);
|
||||
}
|
||||
|
||||
// for more complex assignment cases we need an intermediate register.
|
||||
let (temp_reg, temp_code) = self.allocator.alloc_temp()?;
|
||||
code.append(temp_code);
|
||||
|
||||
// fetch the value of the variable
|
||||
let var_reg = if self.is_global(varname) {
|
||||
let instruction = I::ldw_label(varname.clone(), temp_reg);
|
||||
code.push(instruction);
|
||||
temp_reg
|
||||
} else {
|
||||
let (rg, block) = self.allocator.load_var(varname)?;
|
||||
code.append(block);
|
||||
rg
|
||||
};
|
||||
|
||||
let assign_code = match operator {
|
||||
AssignmentOperator::Assign => {
|
||||
unreachable!("assignment was already checked earlier.")
|
||||
}
|
||||
AssignmentOperator::AddAssign => {
|
||||
I::add(var_reg, result_reg, temp_reg)
|
||||
}
|
||||
AssignmentOperator::SubAssign => {
|
||||
I::sub(var_reg, result_reg, temp_reg)
|
||||
}
|
||||
AssignmentOperator::MulAssign => {
|
||||
return Err(CompilerError::Unimplemented(
|
||||
"TODO: implement multiplication for assignment".to_string(),
|
||||
));
|
||||
}
|
||||
AssignmentOperator::DivAssign => {
|
||||
return Err(CompilerError::Unimplemented(
|
||||
"TODO: write proper div function for DSA".to_string(),
|
||||
));
|
||||
}
|
||||
AssignmentOperator::ModAssign => {
|
||||
return Err(CompilerError::Unimplemented(
|
||||
"TODO: write proper mod function for DSA".to_string(),
|
||||
));
|
||||
}
|
||||
AssignmentOperator::AndAssign => {
|
||||
I::and(var_reg, result_reg, temp_reg)
|
||||
}
|
||||
AssignmentOperator::OrAssign => I::or(var_reg, result_reg, temp_reg),
|
||||
AssignmentOperator::XorAssign => {
|
||||
I::xor(var_reg, result_reg, temp_reg)
|
||||
}
|
||||
AssignmentOperator::LeftShiftAssign => {
|
||||
// this is only useful if we optimise out the register allocation
|
||||
// inside value.
|
||||
// if let Expression::Number { value, .. } = *value {
|
||||
// I::shl(var_reg, value, temp_reg)
|
||||
// }
|
||||
I::shl(var_reg, result_reg, 0, temp_reg)
|
||||
}
|
||||
AssignmentOperator::RightShiftAssign => {
|
||||
// this is only useful if we optimise out the register allocation
|
||||
// if let Expression::Number { value, .. } = *value {
|
||||
// I::shr(var_reg, value, temp_reg)
|
||||
// }
|
||||
I::shr(var_reg, result_reg, 0, temp_reg)
|
||||
}
|
||||
};
|
||||
code.push(assign_code);
|
||||
|
||||
// Check if this is a global variable
|
||||
if self.is_global(varname) {
|
||||
// Store to global label
|
||||
code.push(I::stw_label(temp_reg, varname.clone()))
|
||||
} else {
|
||||
// Store result in local variable
|
||||
let store_code = self.allocator.store_var(varname, &temp_reg);
|
||||
code.append(store_code);
|
||||
}
|
||||
|
||||
self.allocator.free_temp(result_reg);
|
||||
self.allocator.free_temp(temp_reg);
|
||||
}
|
||||
|
||||
Statement::Return(expr) => {
|
||||
if let Some(e) = expr {
|
||||
let (result_reg, expr_code) =
|
||||
self.generate_expression(e, true, func_body)?;
|
||||
code.append(expr_code);
|
||||
code.push(I::stw_reg_offset(result_reg, Register::Bpr, 8));
|
||||
code.push(I::jmp("_ret"));
|
||||
self.allocator.free_temp(result_reg);
|
||||
}
|
||||
}
|
||||
|
||||
Statement::If {
|
||||
condition,
|
||||
then_stmt,
|
||||
else_stmt,
|
||||
} => {
|
||||
// Generate condition
|
||||
let (cond_reg, cond_code) =
|
||||
self.generate_expression(condition, true, func_body)?;
|
||||
code.append(cond_code);
|
||||
|
||||
// Compare with zero
|
||||
code.push(I::cmp(cond_reg, Register::Zero));
|
||||
self.allocator.free_temp(cond_reg);
|
||||
|
||||
// Generate unique labels
|
||||
let then_label = format!("_then_{}", self.get_unique_label());
|
||||
let else_label = format!("_else_{}", self.get_unique_label());
|
||||
let end_label = format!("_end_{}", self.get_unique_label());
|
||||
|
||||
// Jump to else if condition is false (equal to zero)
|
||||
code.push(I::jeq(else_label.clone()));
|
||||
|
||||
// Then block
|
||||
code.push(I::label(then_label));
|
||||
for s in then_stmt {
|
||||
code.append(self.generate_statement(s, func_body)?);
|
||||
}
|
||||
|
||||
if then_stmt.is_empty() {
|
||||
code.push(I::Nop);
|
||||
}
|
||||
|
||||
code.push(I::jmp(end_label.clone()));
|
||||
|
||||
// Else block
|
||||
code.push(I::label(else_label));
|
||||
for s in else_stmt {
|
||||
code.append(self.generate_statement(s, func_body)?);
|
||||
}
|
||||
|
||||
if else_stmt.is_empty() {
|
||||
code.push(I::Nop);
|
||||
}
|
||||
|
||||
code.push(I::label(end_label));
|
||||
}
|
||||
|
||||
Statement::While { condition, body } => {
|
||||
let loop_start = format!("_while_start_{}", self.get_unique_label());
|
||||
let loop_end = format!("_while_end_{}", self.get_unique_label());
|
||||
|
||||
code.push(I::label(&loop_start));
|
||||
|
||||
// Generate condition
|
||||
let (cond_reg, cond_code) =
|
||||
self.generate_expression(condition, true, func_body)?;
|
||||
code.append(cond_code);
|
||||
|
||||
code.push(I::cmp(cond_reg, Register::Zero));
|
||||
self.allocator.free_temp(cond_reg);
|
||||
|
||||
code.push(I::jeq(loop_end.clone()));
|
||||
|
||||
// Loop body
|
||||
for s in body {
|
||||
code.append(self.generate_statement(s, func_body)?);
|
||||
}
|
||||
|
||||
code.push(I::jmp(loop_start));
|
||||
code.push(I::label(loop_end));
|
||||
}
|
||||
|
||||
Statement::Loop(body) => {
|
||||
let loop_start = format!("_loop_start_{}", self.get_unique_label());
|
||||
code.push(I::label(&loop_start));
|
||||
|
||||
for s in body {
|
||||
code.append(self.generate_statement(s, func_body)?);
|
||||
}
|
||||
|
||||
code.push(I::jmp(loop_start));
|
||||
}
|
||||
|
||||
Statement::Expression { expr } => {
|
||||
let (result_reg, expr_code) =
|
||||
self.generate_expression(expr, false, func_body)?;
|
||||
code.append(expr_code);
|
||||
self.allocator.free_temp(result_reg);
|
||||
}
|
||||
|
||||
Statement::Block(statements) => {
|
||||
for s in statements {
|
||||
code.append(self.generate_statement(s, func_body)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(code)
|
||||
}
|
||||
|
||||
// Example: Generate code for an expression
|
||||
// Returns (register containing result, assembly code)
|
||||
fn generate_expression(
|
||||
&mut self,
|
||||
expr: &Expression,
|
||||
use_result: bool,
|
||||
func_body: &mut IB,
|
||||
) -> Result<(Register, IB), CompilerError> {
|
||||
let mut code = IB::new();
|
||||
|
||||
match expr {
|
||||
Expression::Empty => Ok((Register::Null, code)),
|
||||
|
||||
Expression::Number(n) => match n {
|
||||
Number::Signed(value, _) => {
|
||||
let (reg, alloc_code) = self.allocator.alloc_temp()?;
|
||||
code.append(alloc_code);
|
||||
|
||||
// Load immediate value
|
||||
code.push(I::lwi(*value as u32, reg));
|
||||
|
||||
Ok((reg, code))
|
||||
}
|
||||
Number::Unsigned(value, _) => {
|
||||
let (reg, alloc_code) = self.allocator.alloc_temp()?;
|
||||
code.append(alloc_code);
|
||||
|
||||
// Load immediate value
|
||||
code.push(I::lwi(*value as u32, reg));
|
||||
|
||||
Ok((reg, code))
|
||||
}
|
||||
},
|
||||
|
||||
Expression::CharLiteral(value) => {
|
||||
let (reg, alloc_code) = self.allocator.alloc_temp()?;
|
||||
code.append(alloc_code);
|
||||
|
||||
// Load immediate value
|
||||
code.push(I::comment(format!("char literal '{value}'")));
|
||||
code.push(I::lwi(*value as u32, reg));
|
||||
|
||||
Ok((reg, code))
|
||||
}
|
||||
|
||||
Expression::StringLiteral(value) => {
|
||||
let (reg, alloc_code) = self.allocator.alloc_temp()?;
|
||||
code.append(alloc_code);
|
||||
|
||||
// write string into memory
|
||||
let uuid = self.get_unique_label();
|
||||
|
||||
func_body.insert(0, I::db_string(format!("str_{uuid}"), value));
|
||||
|
||||
// Load pointer to string
|
||||
code.push(I::lwi_label(format!("str_{uuid}"), reg));
|
||||
|
||||
Ok((reg, code))
|
||||
}
|
||||
|
||||
Expression::ArrayLiteral { elements, type_id } => todo!(),
|
||||
Expression::StructLiteral {
|
||||
name,
|
||||
fields,
|
||||
type_id,
|
||||
} => todo!(),
|
||||
|
||||
Expression::Variable { name, .. } => {
|
||||
if self.is_global(&name.name) {
|
||||
// Allocate a temporary register for the global
|
||||
let (reg, alloc_code) = self.allocator.alloc_temp()?;
|
||||
code.append(alloc_code);
|
||||
|
||||
// Load from global label
|
||||
code.push(I::ldw_label(name.name.clone(), reg));
|
||||
|
||||
Ok((reg, code))
|
||||
} else {
|
||||
// Local variable - use existing allocator logic
|
||||
let (reg, load_code) = self.allocator.load_var(&name.name)?;
|
||||
code.append(load_code);
|
||||
Ok((reg, code))
|
||||
}
|
||||
}
|
||||
|
||||
Expression::Binary {
|
||||
op, left, right, ..
|
||||
} => {
|
||||
// Evaluate left operand
|
||||
let (left_reg, left_code) =
|
||||
self.generate_expression(left, true, func_body)?;
|
||||
code.append(left_code);
|
||||
|
||||
// Evaluate right operand
|
||||
let (right_reg, right_code) =
|
||||
self.generate_expression(right, true, func_body)?;
|
||||
code.append(right_code);
|
||||
|
||||
// Allocate result register
|
||||
let (result_reg, result_alloc) = self.allocator.alloc_temp()?;
|
||||
code.append(result_alloc);
|
||||
|
||||
// Generate operation
|
||||
match op {
|
||||
BinaryOperator::Add => {
|
||||
code.push(I::add(left_reg, right_reg, result_reg));
|
||||
}
|
||||
BinaryOperator::Sub => {
|
||||
code.push(I::sub(left_reg, right_reg, result_reg));
|
||||
}
|
||||
BinaryOperator::Mul => {
|
||||
self.include("maths", "./lib/maths/core.dsa");
|
||||
// Call multiply function
|
||||
code.push(I::push(right_reg));
|
||||
code.push(I::push(left_reg));
|
||||
code.push(I::call("maths::multiply"));
|
||||
code.push(I::pop(result_reg));
|
||||
code.push(I::pop(Register::Zero));
|
||||
}
|
||||
BinaryOperator::Div => {
|
||||
return Err(CompilerError::Unimplemented(
|
||||
"TODO: write proper div function for DSA".to_string(),
|
||||
));
|
||||
// self.include("maths", "./lib/maths/core.dsa");
|
||||
// // Call divide function
|
||||
// code.push(format!("\tpush {}", right_reg));
|
||||
// code.push(format!("\tpush {}", left_reg));
|
||||
// code.push("\tcall maths::divide".to_string());
|
||||
// code.push(format!("\tpop {}", result_reg));
|
||||
// code.push("\tpop zero".to_string());
|
||||
}
|
||||
BinaryOperator::Mod => {
|
||||
return Err(CompilerError::Unimplemented(
|
||||
"TODO: write proper mod function for DSA".to_string(),
|
||||
));
|
||||
// self.include("maths", "./lib/maths/core.dsa");
|
||||
// // Call modulo function
|
||||
// code.push(format!("\tpush {}", right_reg));
|
||||
// code.push(format!("\tpush {}", left_reg));
|
||||
// code.push("\tcall maths::modulo".to_string());
|
||||
// code.push(format!("\tpop {}", result_reg));
|
||||
// code.push("\tpop zero".to_string());
|
||||
}
|
||||
BinaryOperator::BitwiseAnd => {
|
||||
code.push(I::and(left_reg, right_reg, result_reg));
|
||||
}
|
||||
BinaryOperator::BitwiseOr => {
|
||||
code.push(I::or(left_reg, right_reg, result_reg));
|
||||
}
|
||||
BinaryOperator::BitwiseXor => {
|
||||
code.push(I::xor(left_reg, right_reg, result_reg));
|
||||
}
|
||||
BinaryOperator::LogicalAnd => {
|
||||
return Err(CompilerError::Unimplemented(
|
||||
"assembler/ISA does not yet support logical and!".to_string(),
|
||||
));
|
||||
}
|
||||
BinaryOperator::LogicalOr => {
|
||||
return Err(CompilerError::Unimplemented(
|
||||
"assembler/ISA does not yet support logical or!".to_string(),
|
||||
));
|
||||
}
|
||||
BinaryOperator::LeftShift => {
|
||||
code.push(I::shl(left_reg, right_reg, 0, result_reg));
|
||||
}
|
||||
BinaryOperator::RightShift => {
|
||||
code.push(I::shr(left_reg, right_reg, 0, result_reg));
|
||||
}
|
||||
// Comparison operators - return 1 (true) or 0 (false)
|
||||
BinaryOperator::Equal => {
|
||||
code.push(I::cmp(left_reg, right_reg));
|
||||
code.push(I::lwi(1, result_reg));
|
||||
let end_label = format!("_cmp_end_{}", self.get_unique_label());
|
||||
code.push(I::jeq(end_label.clone()));
|
||||
code.push(I::lwi(0, result_reg));
|
||||
code.push(I::label(end_label));
|
||||
}
|
||||
BinaryOperator::NotEqual => {
|
||||
code.push(I::cmp(left_reg, right_reg));
|
||||
code.push(I::lwi(1, result_reg));
|
||||
let end_label = format!("_cmp_end_{}", self.get_unique_label());
|
||||
code.push(I::Jne {
|
||||
target: Label(end_label.clone()),
|
||||
});
|
||||
code.push(I::lwi(0, result_reg));
|
||||
code.push(I::label(&end_label));
|
||||
}
|
||||
BinaryOperator::LessThan => {
|
||||
code.push(I::cmp(left_reg, right_reg));
|
||||
code.push(I::lwi(1, result_reg));
|
||||
let end_label = format!("_cmp_end_{}", self.get_unique_label());
|
||||
code.push(I::Jlt {
|
||||
target: Label(end_label.clone()),
|
||||
});
|
||||
code.push(I::lwi(0, result_reg));
|
||||
code.push(I::label(&end_label));
|
||||
}
|
||||
BinaryOperator::LessOrEqual => {
|
||||
code.push(I::cmp(left_reg, right_reg));
|
||||
code.push(I::lwi(1, result_reg));
|
||||
let end_label = format!("_cmp_end_{}", self.get_unique_label());
|
||||
code.push(I::Jle {
|
||||
target: Label(end_label.clone()),
|
||||
});
|
||||
code.push(I::lwi(0, result_reg));
|
||||
code.push(I::label(&end_label));
|
||||
}
|
||||
BinaryOperator::GreaterThan => {
|
||||
code.push(I::cmp(left_reg, right_reg));
|
||||
code.push(I::lwi(1, result_reg));
|
||||
let end_label = format!("_cmp_end_{}", self.get_unique_label());
|
||||
code.push(I::Jgt {
|
||||
target: Label(end_label.clone()),
|
||||
});
|
||||
code.push(I::lwi(0, result_reg));
|
||||
code.push(I::label(&end_label));
|
||||
}
|
||||
BinaryOperator::GreaterOrEqual => {
|
||||
code.push(I::cmp(left_reg, right_reg));
|
||||
code.push(I::lwi(1, result_reg));
|
||||
let end_label = format!("_cmp_end_{}", self.get_unique_label());
|
||||
code.push(I::Jge {
|
||||
target: Label(end_label.clone()),
|
||||
});
|
||||
code.push(I::lwi(0, result_reg));
|
||||
code.push(I::label(&end_label));
|
||||
} // _ => unimplemented!(),
|
||||
}
|
||||
|
||||
// Free operand registers (allocator will protect variables)
|
||||
self.allocator.free_temp(left_reg);
|
||||
self.allocator.free_temp(right_reg);
|
||||
|
||||
Ok((result_reg, code))
|
||||
}
|
||||
|
||||
Expression::UnaryPostfix { op, operand, .. } => {
|
||||
let (operand_reg, operand_code) =
|
||||
self.generate_expression(operand, true, func_body)?;
|
||||
code.append(operand_code);
|
||||
|
||||
let (result_reg, result_alloc) = self.allocator.alloc_temp()?;
|
||||
code.append(result_alloc);
|
||||
|
||||
match op {
|
||||
UnaryOperator::Increment => {
|
||||
// postfix increment - return old value
|
||||
code.push(I::mov(operand_reg, result_reg));
|
||||
}
|
||||
UnaryOperator::Decrement => {
|
||||
// postfix decrement - return old value
|
||||
code.push(I::mov(operand_reg, result_reg));
|
||||
}
|
||||
_ => {
|
||||
return Err(CompilerError::Generic(format!(
|
||||
"{op} is prefix only!"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
self.allocator.free_temp(operand_reg);
|
||||
Ok((result_reg, code))
|
||||
}
|
||||
|
||||
Expression::Unary { op, operand, .. } => {
|
||||
let (operand_reg, operand_code) =
|
||||
self.generate_expression(operand, true, func_body)?;
|
||||
code.append(operand_code);
|
||||
|
||||
let (result_reg, result_alloc) = self.allocator.alloc_temp()?;
|
||||
code.append(result_alloc);
|
||||
|
||||
match op {
|
||||
UnaryOperator::Minus => {
|
||||
// Negate: result = 0 - operand
|
||||
code.push(I::sub(Register::Zero, operand_reg, result_reg));
|
||||
}
|
||||
UnaryOperator::Plus => {
|
||||
// Just move
|
||||
code.push(I::mov(operand_reg, result_reg));
|
||||
}
|
||||
UnaryOperator::Dereference => {
|
||||
code.push(I::ldw_reg(operand_reg, result_reg));
|
||||
}
|
||||
UnaryOperator::AddressOf => {
|
||||
// ensure the referenced variable is on the stack and return its
|
||||
// address.
|
||||
let (offset, alloc_code) =
|
||||
self.allocator.free_register(&operand_reg)?;
|
||||
code.push(alloc_code);
|
||||
code.push(I::iadd_dest(
|
||||
Register::Spr,
|
||||
offset - self.allocator.get_stack_offset(),
|
||||
result_reg,
|
||||
));
|
||||
}
|
||||
UnaryOperator::SizeOf => {
|
||||
if let Ok(id) = operand.type_id() {
|
||||
let size = id.size();
|
||||
code.push(I::lwi(size as u32, result_reg));
|
||||
}
|
||||
}
|
||||
UnaryOperator::Increment => {
|
||||
// prefix increment
|
||||
code.push(I::mov(operand_reg, result_reg));
|
||||
code.push(I::iadd_dest(operand_reg, 1, result_reg));
|
||||
}
|
||||
UnaryOperator::Decrement => {
|
||||
// prefix decrement
|
||||
code.push(I::mov(operand_reg, result_reg));
|
||||
code.push(I::iadd_dest(operand_reg, -1, result_reg));
|
||||
}
|
||||
UnaryOperator::BitwiseNot => {
|
||||
code.push(I::not(operand_reg, result_reg));
|
||||
}
|
||||
UnaryOperator::LogicalNot => {
|
||||
return Err(CompilerError::Unimplemented(
|
||||
"Assembler/ISA does not yet support logical not".to_string(),
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
return Err(CompilerError::Generic(format!(
|
||||
"{op} is postfix only!"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
self.allocator.free_temp(operand_reg);
|
||||
Ok((result_reg, code))
|
||||
}
|
||||
|
||||
Expression::Call {
|
||||
func: Call { name, args },
|
||||
..
|
||||
} => {
|
||||
// first evaluate all the args we're going to need
|
||||
let mut arg_regs = Vec::new();
|
||||
for arg in args.iter().rev() {
|
||||
let (arg_reg, arg_code) =
|
||||
self.generate_expression(arg, true, func_body)?;
|
||||
code.append(arg_code);
|
||||
arg_regs.push(arg_reg);
|
||||
}
|
||||
|
||||
// Save caller-saved registers and track which ones we saved
|
||||
let saved_regs = self.allocator.get_caller_saved_registers();
|
||||
for reg in &saved_regs {
|
||||
// spill variables to stack
|
||||
code.push(self.allocator.free_register(reg).unwrap().1);
|
||||
}
|
||||
|
||||
// Evaluate and push arguments in reverse order
|
||||
for (i, arg_reg) in arg_regs.iter().enumerate() {
|
||||
code.push(I::comment(format!("push arg {}", args.len() - 1 - i)));
|
||||
code.push(I::push(*arg_reg));
|
||||
}
|
||||
|
||||
if self.symbols.contains(&name.name) {
|
||||
// Call local function
|
||||
code.push(I::call(name.to_string()));
|
||||
} else if let Some(ns) = name.namespace.clone()
|
||||
&& self.imports.contains_key(&ns)
|
||||
{
|
||||
code.push(I::call(name.to_string()));
|
||||
} else {
|
||||
return Err(CompilerError::Undefined(name.clone()));
|
||||
}
|
||||
|
||||
let result_reg: Register;
|
||||
|
||||
if use_result {
|
||||
let (temp_result_reg, result_alloc) = self.allocator.alloc_temp()?;
|
||||
result_reg = temp_result_reg;
|
||||
|
||||
code.append(result_alloc);
|
||||
code.push(I::pop(result_reg));
|
||||
|
||||
// Clean up arguments
|
||||
if args.len() > 1 {
|
||||
for _ in 0..(args.len() - 1) {
|
||||
code.push(I::pop(Register::Zero));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result_reg = Register::Zero;
|
||||
|
||||
// Clean up arguments
|
||||
if args.len() > 0 {
|
||||
for _ in 0..(args.len()) {
|
||||
code.push(I::pop(Register::Zero));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Free argument registers
|
||||
for reg in arg_regs {
|
||||
self.allocator.free_temp(reg);
|
||||
}
|
||||
|
||||
Ok((result_reg, code))
|
||||
}
|
||||
|
||||
Expression::IndexAccess {
|
||||
expr,
|
||||
index,
|
||||
type_id,
|
||||
} => {
|
||||
let (expr_reg, expr_alloc) =
|
||||
self.generate_expression(expr, true, func_body)?;
|
||||
code.append(expr_alloc);
|
||||
|
||||
let (index_reg, index_alloc) =
|
||||
self.generate_expression(index, true, func_body)?;
|
||||
code.append(index_alloc);
|
||||
|
||||
let (result_reg, result_alloc) = self.allocator.alloc_temp()?;
|
||||
code.append(result_alloc);
|
||||
|
||||
// add the expr pointer to the index to get the final address.
|
||||
code.push(I::add(expr_reg, index_reg, result_reg));
|
||||
// load the value at the address.
|
||||
code.push(I::ldw_reg(result_reg, result_reg));
|
||||
|
||||
self.allocator.free_temp(expr_reg);
|
||||
self.allocator.free_temp(index_reg);
|
||||
|
||||
Ok((result_reg, code))
|
||||
}
|
||||
Expression::MemberAccess {
|
||||
expr,
|
||||
field_name,
|
||||
type_id,
|
||||
} => Err(CompilerError::Unimplemented(
|
||||
"Structs are not yet implemented!".to_string(),
|
||||
)),
|
||||
|
||||
Expression::TypeCast {
|
||||
expr,
|
||||
target_type,
|
||||
type_id,
|
||||
} => {
|
||||
let (expr_reg, expr_code) =
|
||||
self.generate_expression(expr, true, func_body)?;
|
||||
|
||||
// not sure if we actually need to do anything here.
|
||||
// for now we just return the previous expression.
|
||||
Ok((expr_reg, expr_code))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper for generating unique labels
|
||||
fn get_unique_label(&mut self) -> String {
|
||||
// You'd implement a counter here
|
||||
static COUNTER: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
let val = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
(val + 1).to_string()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,797 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::backend::dsa::registers::Register;
|
||||
|
||||
pub struct InsBlock {
|
||||
instructions: Vec<Instruction>,
|
||||
}
|
||||
|
||||
impl InsBlock {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
instructions: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, index: usize, instr: Instruction) {
|
||||
self.instructions.insert(index, instr);
|
||||
}
|
||||
|
||||
pub fn push(&mut self, instr: Instruction) {
|
||||
self.instructions.push(instr);
|
||||
}
|
||||
|
||||
pub fn append(&mut self, mut other: Self) {
|
||||
self.instructions.append(&mut other.instructions);
|
||||
}
|
||||
|
||||
pub fn extend(&mut self, instrs: impl IntoIterator<Item = Instruction>) {
|
||||
self.instructions.extend(instrs);
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.instructions.is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.instructions.len()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &Instruction> {
|
||||
self.instructions.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Instruction>> for InsBlock {
|
||||
fn from(instructions: Vec<Instruction>) -> Self {
|
||||
Self { instructions }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Instruction> for InsBlock {
|
||||
fn from(instr: Instruction) -> Self {
|
||||
Self {
|
||||
instructions: vec![instr],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Instruction {
|
||||
// Labels and comments
|
||||
Label(Label),
|
||||
Comment {
|
||||
text: String,
|
||||
top_level: bool,
|
||||
},
|
||||
Newline,
|
||||
|
||||
// Data Directives
|
||||
Db {
|
||||
label: String,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
Dh {
|
||||
label: String,
|
||||
data: Vec<u16>,
|
||||
},
|
||||
Dw {
|
||||
label: String,
|
||||
data: Vec<u32>,
|
||||
},
|
||||
DString {
|
||||
// alias for db.
|
||||
label: String,
|
||||
data: String,
|
||||
},
|
||||
|
||||
Resx {
|
||||
label: String,
|
||||
size: u32,
|
||||
},
|
||||
|
||||
// Include
|
||||
Include {
|
||||
name: String,
|
||||
path: String,
|
||||
},
|
||||
|
||||
// Data movement
|
||||
Mov {
|
||||
src: Register,
|
||||
dest: Register,
|
||||
},
|
||||
Movs {
|
||||
src: Register,
|
||||
dest: Register,
|
||||
},
|
||||
|
||||
// Memory operations
|
||||
Ldb {
|
||||
src: MemOperand,
|
||||
dest: Register,
|
||||
},
|
||||
Ldh {
|
||||
src: MemOperand,
|
||||
dest: Register,
|
||||
},
|
||||
Ldw {
|
||||
src: MemOperand,
|
||||
dest: Register,
|
||||
},
|
||||
Stb {
|
||||
src: Register,
|
||||
dest: MemOperand,
|
||||
},
|
||||
Sth {
|
||||
src: Register,
|
||||
dest: MemOperand,
|
||||
},
|
||||
Stw {
|
||||
src: Register,
|
||||
dest: MemOperand,
|
||||
},
|
||||
|
||||
// Immediate loads
|
||||
Lli {
|
||||
imm: Imm,
|
||||
dest: Register,
|
||||
},
|
||||
Lui {
|
||||
imm: Imm,
|
||||
dest: Register,
|
||||
},
|
||||
Lwi {
|
||||
imm: Imm,
|
||||
dest: Register,
|
||||
},
|
||||
LwiLabel {
|
||||
label: String,
|
||||
dest: Register,
|
||||
},
|
||||
|
||||
// Arithmetic
|
||||
Add {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Register,
|
||||
},
|
||||
Sub {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Register,
|
||||
},
|
||||
IAdd {
|
||||
src: Register,
|
||||
imm: Imm,
|
||||
dest: Option<Register>,
|
||||
},
|
||||
ISub {
|
||||
src: Register,
|
||||
imm: Imm,
|
||||
dest: Option<Register>,
|
||||
},
|
||||
Inc {
|
||||
reg: Register,
|
||||
},
|
||||
Dec {
|
||||
reg: Register,
|
||||
},
|
||||
|
||||
// Bitwise
|
||||
And {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Register,
|
||||
},
|
||||
Or {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Register,
|
||||
},
|
||||
Xor {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Register,
|
||||
},
|
||||
Not {
|
||||
src: Register,
|
||||
dest: Register,
|
||||
},
|
||||
Nand {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Register,
|
||||
},
|
||||
Nor {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Register,
|
||||
},
|
||||
Xnor {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Register,
|
||||
},
|
||||
|
||||
// Shifts
|
||||
Shl {
|
||||
src1: Register,
|
||||
r_shamt: Register,
|
||||
i_shamt: u16,
|
||||
dest: Register,
|
||||
},
|
||||
Shr {
|
||||
src1: Register,
|
||||
r_shamt: Register,
|
||||
i_shamt: u16,
|
||||
dest: Register,
|
||||
},
|
||||
|
||||
// Comparison
|
||||
Cmp {
|
||||
reg1: Register,
|
||||
reg2: Register,
|
||||
},
|
||||
|
||||
// Jumps
|
||||
Jmp {
|
||||
target: Label,
|
||||
},
|
||||
Jeq {
|
||||
target: Label,
|
||||
},
|
||||
Jne {
|
||||
target: Label,
|
||||
},
|
||||
Jgt {
|
||||
target: Label,
|
||||
},
|
||||
Jge {
|
||||
target: Label,
|
||||
},
|
||||
Jlt {
|
||||
target: Label,
|
||||
},
|
||||
Jle {
|
||||
target: Label,
|
||||
},
|
||||
|
||||
// Stack
|
||||
Push {
|
||||
reg: Register,
|
||||
},
|
||||
Pop {
|
||||
reg: Register,
|
||||
},
|
||||
|
||||
// Function calls
|
||||
Call {
|
||||
target: String,
|
||||
}, // namespace::function
|
||||
Return,
|
||||
|
||||
// System
|
||||
Hlt,
|
||||
Nop,
|
||||
Int {
|
||||
code: u8,
|
||||
},
|
||||
}
|
||||
|
||||
pub enum DataDirective {
|
||||
U8(Vec<u8>),
|
||||
U16(Vec<u16>),
|
||||
U32(Vec<u32>),
|
||||
String(String),
|
||||
Char(char),
|
||||
}
|
||||
|
||||
impl fmt::Display for Instruction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Label(l) => write!(f, "{}:", l),
|
||||
Self::Newline => write!(f, ""), /* empty string as newlines are inserted */
|
||||
// automatically.
|
||||
Self::Comment { text, top_level } => write!(
|
||||
f,
|
||||
"{}",
|
||||
text.lines()
|
||||
.map(|line| format!(
|
||||
"{}// {}",
|
||||
if *top_level { "" } else { " " },
|
||||
line.trim(),
|
||||
))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
),
|
||||
|
||||
Self::Include { name, path } => write!(f, "include {name}: \"{}\"", path),
|
||||
|
||||
Self::Db { label, data } => write!(
|
||||
f,
|
||||
"db {}: {}",
|
||||
label,
|
||||
data.iter()
|
||||
.map(|&b| format!("{:#04X}", b))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
),
|
||||
Self::Dh { label, data } => write!(
|
||||
f,
|
||||
"dh {}: {}",
|
||||
label,
|
||||
data.iter()
|
||||
.map(|&b| format!("{:#06X}", b))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
),
|
||||
Self::Dw { label, data } => write!(
|
||||
f,
|
||||
"dw {}: {}",
|
||||
label,
|
||||
data.iter()
|
||||
.map(|&b| format!("{:#08X}", b))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
),
|
||||
Self::DString { label, data } => write!(f, "db {}: \"{}\"", label, data),
|
||||
|
||||
Self::Resx { label, size } => write!(f, "resx {}: {}", label, size),
|
||||
|
||||
Self::Mov { src, dest } => write!(f, " mov {}, {}", src, dest),
|
||||
Self::Movs { src, dest } => write!(f, " movs {}, {}", src, dest),
|
||||
|
||||
Self::Ldb { src: addr, dest } => {
|
||||
let (reg, offset) = reg_and_offset(addr);
|
||||
write!(f, " ldb {}, {}, {}", reg, dest, offset)
|
||||
}
|
||||
Self::Ldh { src: addr, dest } => {
|
||||
let (reg, offset) = reg_and_offset(addr);
|
||||
write!(f, " ldh {}, {}, {}", reg, dest, offset)
|
||||
}
|
||||
Self::Ldw { src, dest } => {
|
||||
let (reg, offset) = reg_and_offset(src);
|
||||
write!(f, " ldw {}, {}, {}", reg, dest, offset)
|
||||
}
|
||||
// Self::Ldbs { addr, dest } => {
|
||||
// write!(f, " ldbs {}, {}", format_mem_operand(addr), dest)
|
||||
// }
|
||||
// Self::Ldhs { addr, dest } => {
|
||||
// write!(f, " ldhs {}, {}", format_mem_operand(addr), dest)
|
||||
// }
|
||||
// Self::Ldws { addr, dest } => {
|
||||
// write!(f, " ldws {}, {}", format_mem_operand(addr), dest)
|
||||
// }
|
||||
Self::Stb { src, dest: addr } => {
|
||||
let (reg, offset) = reg_and_offset(addr);
|
||||
write!(f, " stb {}, {}, {}", src, reg, offset)
|
||||
}
|
||||
Self::Sth { src, dest: addr } => {
|
||||
let (reg, offset) = reg_and_offset(addr);
|
||||
write!(f, " sth {}, {}, {}", src, reg, offset)
|
||||
}
|
||||
Self::Stw { src, dest: addr } => {
|
||||
let (reg, offset) = reg_and_offset(addr);
|
||||
write!(f, " stw {}, {}, {}", src, reg, offset)
|
||||
}
|
||||
|
||||
Self::Lli { imm, dest } => write!(f, " lli {}, {}", imm, dest),
|
||||
Self::Lui { imm, dest } => write!(f, " lui {}, {}", imm, dest),
|
||||
Self::Lwi { imm, dest } => write!(f, " lwi {}, {}", imm, dest),
|
||||
Self::LwiLabel { label, dest } => write!(f, " lwi {}, {}", label, dest),
|
||||
|
||||
// arithmetic
|
||||
Self::Add { src1, src2, dest } => {
|
||||
write!(f, " add {}, {}, {}", src1, src2, dest)
|
||||
}
|
||||
Self::Sub { src1, src2, dest } => {
|
||||
write!(f, " sub {}, {}, {}", src1, src2, dest)
|
||||
}
|
||||
Self::And { src1, src2, dest } => {
|
||||
write!(f, " and {}, {}, {}", src1, src2, dest)
|
||||
}
|
||||
Self::Or { src1, src2, dest } => {
|
||||
write!(f, " or {}, {}, {}", src1, src2, dest)
|
||||
}
|
||||
Self::Nand { src1, src2, dest } => {
|
||||
write!(f, " nand {}, {}, {}", src1, src2, dest)
|
||||
}
|
||||
Self::Xor { src1, src2, dest } => {
|
||||
write!(f, " xor {}, {}, {}", src1, src2, dest)
|
||||
}
|
||||
Self::Nor { src1, src2, dest } => {
|
||||
write!(f, " nor {}, {}, {}", src1, src2, dest)
|
||||
}
|
||||
Self::Not { src, dest } => {
|
||||
write!(f, " not {} {}", src, dest)
|
||||
}
|
||||
Self::Xnor { src1, src2, dest } => {
|
||||
write!(f, " xnor {}, {}, {}", src1, src2, dest)
|
||||
}
|
||||
Self::IAdd { src, imm, dest } => {
|
||||
if let Some(d) = dest {
|
||||
write!(f, " addi {}, {}, {}", src, imm, d)
|
||||
} else {
|
||||
write!(f, " addi {}, {}", src, imm)
|
||||
}
|
||||
}
|
||||
Self::ISub { src, imm, dest } => {
|
||||
if let Some(d) = dest {
|
||||
write!(f, " subi {}, {}, {}", src, imm, d)
|
||||
} else {
|
||||
write!(f, " subi {}, {}", src, imm)
|
||||
}
|
||||
}
|
||||
|
||||
// shift instructions
|
||||
Self::Shl {
|
||||
src1,
|
||||
r_shamt,
|
||||
i_shamt,
|
||||
dest,
|
||||
} => {
|
||||
write!(f, " shl {}, {}, {}, {}", src1, r_shamt, i_shamt, dest)
|
||||
}
|
||||
Self::Shr {
|
||||
src1,
|
||||
r_shamt,
|
||||
i_shamt,
|
||||
dest,
|
||||
} => {
|
||||
write!(f, " shl {}, {}, {}, {}", src1, r_shamt, i_shamt, dest)
|
||||
}
|
||||
|
||||
// increment instructions
|
||||
Self::Inc { reg } => write!(f, " inc {}", reg),
|
||||
Self::Dec { reg } => write!(f, " dec {}", reg),
|
||||
|
||||
Self::Cmp { reg1, reg2 } => write!(f, " cmp {}, {}", reg1, reg2),
|
||||
|
||||
// jump instructions
|
||||
Self::Jmp { target } => write!(f, " jmp {}", target),
|
||||
Self::Jeq { target } => write!(f, " jeq {}", target),
|
||||
Self::Jne { target } => write!(f, " jne {}", target),
|
||||
Self::Jgt { target } => write!(f, " jgt {}", target),
|
||||
Self::Jge { target } => write!(f, " jge {}", target),
|
||||
Self::Jlt { target } => write!(f, " jlt {}", target),
|
||||
Self::Jle { target } => write!(f, " jle {}", target),
|
||||
|
||||
// stack pseudoinstructions
|
||||
Self::Push { reg } => write!(f, " push {}", reg),
|
||||
Self::Pop { reg } => write!(f, " pop {}", reg),
|
||||
|
||||
// call & return pseudoinstructions
|
||||
Self::Call { target } => write!(f, " call {}", target),
|
||||
Self::Return => write!(f, " return"),
|
||||
|
||||
// misc instructions
|
||||
Self::Int { code } => write!(f, " int {}", code),
|
||||
Self::Hlt => write!(f, " hlt"),
|
||||
Self::Nop => write!(f, " nop"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
// data directives
|
||||
pub fn db_string(label: impl Into<String>, data: impl Into<String>) -> Self {
|
||||
Self::DString {
|
||||
label: label.into(),
|
||||
data: data.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn db_word(label: impl Into<String>, data: u32) -> Self {
|
||||
Self::Dw {
|
||||
label: label.into(),
|
||||
data: vec![data],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn db_bytes(label: impl Into<String>, data: &[u8]) -> Self {
|
||||
Self::Db {
|
||||
label: label.into(),
|
||||
data: data.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
// Movement
|
||||
pub fn mov<R1, R2>(src: R1, dest: R2) -> Self
|
||||
where
|
||||
R1: Into<Register>,
|
||||
R2: Into<Register>,
|
||||
{
|
||||
Self::Mov {
|
||||
src: src.into(),
|
||||
dest: dest.into(),
|
||||
}
|
||||
}
|
||||
|
||||
// Memory loads
|
||||
pub fn ldw_reg<R>(base: R, dest: Register) -> Self
|
||||
where
|
||||
R: Into<Register>,
|
||||
{
|
||||
Self::Ldw {
|
||||
src: MemOperand::RegIndirect(base.into()),
|
||||
dest,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ldw_reg_offset<R>(base: R, dest: Register, offset: i32) -> Self
|
||||
where
|
||||
R: Into<Register>,
|
||||
{
|
||||
Self::Ldw {
|
||||
src: MemOperand::RegOffset(base.into(), offset),
|
||||
dest,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ldw_label(label: impl Into<Label>, dest: Register) -> Self {
|
||||
Self::Ldw {
|
||||
src: MemOperand::Label(label.into()),
|
||||
dest,
|
||||
}
|
||||
}
|
||||
|
||||
// Memory stores
|
||||
pub fn stw_reg<R>(src: Register, base: R) -> Self
|
||||
where
|
||||
R: Into<Register>,
|
||||
{
|
||||
Self::Stw {
|
||||
src,
|
||||
dest: MemOperand::RegIndirect(base.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stw_reg_offset<R>(src: Register, base: R, offset: i32) -> Self
|
||||
where
|
||||
R: Into<Register>,
|
||||
{
|
||||
Self::Stw {
|
||||
src,
|
||||
dest: MemOperand::RegOffset(base.into(), offset),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stw_label(src: Register, label: impl Into<Label>) -> Self {
|
||||
Self::Stw {
|
||||
src,
|
||||
dest: MemOperand::Label(label.into()),
|
||||
}
|
||||
}
|
||||
|
||||
// Arithmetic
|
||||
pub fn add(src1: Register, src2: Register, dest: Register) -> Self {
|
||||
Self::Add { src1, src2, dest }
|
||||
}
|
||||
|
||||
pub fn sub(src1: Register, src2: Register, dest: Register) -> Self {
|
||||
Self::Sub { src1, src2, dest }
|
||||
}
|
||||
|
||||
pub fn and(src1: Register, src2: Register, dest: Register) -> Self {
|
||||
Self::And { src1, src2, dest }
|
||||
}
|
||||
|
||||
pub fn or(src1: Register, src2: Register, dest: Register) -> Self {
|
||||
Self::Or { src1, src2, dest }
|
||||
}
|
||||
|
||||
pub fn xor(src1: Register, src2: Register, dest: Register) -> Self {
|
||||
Self::Xor { src1, src2, dest }
|
||||
}
|
||||
|
||||
pub fn not(src: Register, dest: Register) -> Self {
|
||||
Self::Not { src, dest }
|
||||
}
|
||||
|
||||
pub fn shl(src1: Register, r_shamt: Register, i_shamt: u16, dest: Register) -> Self {
|
||||
Self::Shl {
|
||||
src1,
|
||||
r_shamt,
|
||||
i_shamt,
|
||||
dest,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shr(src1: Register, r_shamt: Register, i_shamt: u16, dest: Register) -> Self {
|
||||
Self::Shr {
|
||||
src1,
|
||||
r_shamt,
|
||||
i_shamt,
|
||||
dest,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iadd(src: Register, value: i64) -> Self {
|
||||
let imm = Imm(value.unsigned_abs() as u32);
|
||||
|
||||
if value < 0 {
|
||||
Self::ISub {
|
||||
src,
|
||||
imm,
|
||||
dest: None,
|
||||
}
|
||||
} else {
|
||||
Self::IAdd {
|
||||
src,
|
||||
imm,
|
||||
dest: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iadd_dest(src: Register, value: i32, dest: Register) -> Self {
|
||||
let imm = Imm(value.unsigned_abs());
|
||||
|
||||
if value < 0 {
|
||||
Self::ISub {
|
||||
src,
|
||||
imm,
|
||||
dest: Some(dest),
|
||||
}
|
||||
} else {
|
||||
Self::IAdd {
|
||||
src,
|
||||
imm,
|
||||
dest: Some(dest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inc(reg: Register) -> Self {
|
||||
Self::Inc { reg }
|
||||
}
|
||||
|
||||
pub fn dec(reg: Register) -> Self {
|
||||
Self::Dec { reg }
|
||||
}
|
||||
|
||||
// Immediate loads
|
||||
pub fn lwi(value: u32, dest: Register) -> Self {
|
||||
if value > 0xFFFF {
|
||||
Self::Lwi {
|
||||
imm: Imm(value),
|
||||
dest,
|
||||
}
|
||||
} else {
|
||||
Self::Lli {
|
||||
imm: Imm(value),
|
||||
dest,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lwi_label(label: impl Into<String>, dest: Register) -> Self {
|
||||
Self::LwiLabel {
|
||||
label: label.into(),
|
||||
dest,
|
||||
}
|
||||
}
|
||||
|
||||
// Control flow
|
||||
pub fn label(name: impl Into<String>) -> Self {
|
||||
Self::Label(Label(name.into()))
|
||||
}
|
||||
|
||||
pub fn jmp(target: impl Into<Label>) -> Self {
|
||||
Self::Jmp {
|
||||
target: target.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn jeq(target: impl Into<Label>) -> Self {
|
||||
Self::Jeq {
|
||||
target: target.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cmp(reg1: Register, reg2: Register) -> Self {
|
||||
Self::Cmp { reg1, reg2 }
|
||||
}
|
||||
|
||||
// Stack
|
||||
pub fn push(reg: Register) -> Self {
|
||||
Self::Push { reg }
|
||||
}
|
||||
|
||||
pub fn pop(reg: Register) -> Self {
|
||||
Self::Pop { reg }
|
||||
}
|
||||
|
||||
// Functions
|
||||
pub fn call(target: impl Into<String>) -> Self {
|
||||
Self::Call {
|
||||
target: target.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn int(code: u8) -> Self {
|
||||
Self::Int { code }
|
||||
}
|
||||
|
||||
pub fn ret() -> Self {
|
||||
Self::Return
|
||||
}
|
||||
|
||||
// Utilities
|
||||
pub fn comment(text: impl Into<String>) -> Self {
|
||||
Self::Comment {
|
||||
text: text.into(),
|
||||
top_level: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn global_comment(text: impl Into<String>) -> Self {
|
||||
Self::Comment {
|
||||
text: text.into(),
|
||||
top_level: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn include(name: impl Into<String>, path: impl Into<String>) -> Self {
|
||||
Self::Include {
|
||||
name: name.into(),
|
||||
path: path.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience trait for Label conversion
|
||||
impl From<String> for Label {
|
||||
fn from(s: String) -> Self {
|
||||
Label(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Label {
|
||||
fn from(s: &str) -> Self {
|
||||
Label(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn reg_and_offset(op: &MemOperand) -> (String, i32) {
|
||||
match op {
|
||||
MemOperand::RegIndirect(reg) => (reg.to_string(), 0),
|
||||
MemOperand::RegOffset(reg, offset) => (reg.to_string(), *offset),
|
||||
MemOperand::Label(label) => (label.to_string(), 0),
|
||||
MemOperand::LabelOffset(label, offset) => (label.to_string(), *offset),
|
||||
}
|
||||
}
|
||||
|
||||
/// Memory operand for loads/stores
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MemOperand {
|
||||
/// Register indirect: [reg]
|
||||
RegIndirect(Register),
|
||||
/// Register with offset: [reg + offset]
|
||||
RegOffset(Register, i32),
|
||||
/// Label: [label]
|
||||
Label(Label),
|
||||
/// Label with offset: [label + offset]
|
||||
LabelOffset(Label, i32),
|
||||
}
|
||||
|
||||
/// Immediate value (16-bit or 32-bit)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Imm(pub u32);
|
||||
|
||||
impl fmt::Display for Imm {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Label reference
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Label(pub String);
|
||||
|
||||
impl fmt::Display for Label {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
use crate::model::{CompilerError, Program};
|
||||
|
||||
mod codegen;
|
||||
mod instruction;
|
||||
mod registers;
|
||||
mod scope;
|
||||
|
||||
pub fn generate_code(ast: &Program) -> Result<String, CompilerError> {
|
||||
let mut codegen = codegen::CodeGenerator::new(ast.clone());
|
||||
codegen.generate()
|
||||
}
|
||||
@@ -0,0 +1,560 @@
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
||||
use crate::{
|
||||
backend::dsa::instruction::{InsBlock, Instruction},
|
||||
model::CompilerError,
|
||||
};
|
||||
|
||||
/// Register allocator for DSA assembly generation
|
||||
/// Manages general-purpose registers (rg0-rgf) and handles stack spilling
|
||||
pub struct RegisterAllocator {
|
||||
/// Available general-purpose registers
|
||||
/// Maps variable names to their current location (register or stack offset)
|
||||
variable_locations: HashMap<String, Location>,
|
||||
|
||||
/// Maps registers to the variables they currently hold
|
||||
register_contents: HashMap<Register, String>,
|
||||
|
||||
/// Current stack offset for local variables (relative to bpr)
|
||||
/// Starts at -4 (going downward from base pointer)
|
||||
stack_offset: i32,
|
||||
|
||||
/// Track which registers are currently in use
|
||||
in_use: Vec<(Register, bool)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Location {
|
||||
register: Option<Register>,
|
||||
stack: Option<i32>,
|
||||
}
|
||||
|
||||
impl Location {
|
||||
pub fn stack(offset: i32) -> Self {
|
||||
Location {
|
||||
register: None,
|
||||
stack: Some(offset),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(register: Register) -> Self {
|
||||
Location {
|
||||
register: Some(register),
|
||||
stack: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RegisterAllocator {
|
||||
pub fn new() -> Self {
|
||||
// Initialize with available GP registers (rg0-rgf = 16 registers)
|
||||
let in_use = vec![
|
||||
Register::Rg0,
|
||||
Register::Rg1,
|
||||
Register::Rg2,
|
||||
Register::Rg3,
|
||||
Register::Rg4,
|
||||
Register::Rg5,
|
||||
Register::Rg6,
|
||||
Register::Rg7,
|
||||
Register::Rg8,
|
||||
Register::Rg9,
|
||||
Register::Rga,
|
||||
Register::Rgb,
|
||||
Register::Rgc,
|
||||
Register::Rgd,
|
||||
Register::Rge,
|
||||
Register::Rgf,
|
||||
]
|
||||
.iter()
|
||||
.map(|®| (reg, false))
|
||||
.collect();
|
||||
|
||||
RegisterAllocator {
|
||||
// available_registers: registers,
|
||||
variable_locations: HashMap::new(),
|
||||
register_contents: HashMap::new(),
|
||||
stack_offset: -4, // Start at -4 (first local below saved bpr)
|
||||
in_use,
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate a temporary register for expression evaluation
|
||||
/// Returns the register name and optionally assembly code to save it
|
||||
pub fn alloc_temp(&mut self) -> Result<(Register, InsBlock), CompilerError> {
|
||||
// Try to find an unused register
|
||||
|
||||
// println!("finding! {:#?}", self.in_use);
|
||||
|
||||
if let Some(reg) = self.find_free_register() {
|
||||
self.in_use[reg as usize].1 = true;
|
||||
return Ok((reg, InsBlock::new()));
|
||||
}
|
||||
|
||||
// All registers in use - need to spill one
|
||||
// Choose the first register with a variable we can spill
|
||||
// Find a register to spill
|
||||
|
||||
// let reg_to_spill = self
|
||||
// .available_registers
|
||||
// .iter()
|
||||
// .find(|reg| self.register_contents.contains_key(*reg))
|
||||
// .cloned();
|
||||
|
||||
// if let Some(reg) = reg_to_spill {
|
||||
// // Spill this variable to stack
|
||||
// let spill_code = self.spill_register(®)?;
|
||||
// code.extend(spill_code);
|
||||
|
||||
// self.in_use.insert(reg.clone(), true);
|
||||
// return Ok((reg, code));
|
||||
// }
|
||||
|
||||
todo!("an efficient stack spilling algorithm. needs scope awareness.");
|
||||
|
||||
Err(CompilerError::Generic(
|
||||
"All registers are used up yet there are no variables to spill to the stack"
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
// fn set_in_use(&mut self, reg: Register, in_use: bool) {
|
||||
// self.in_use[reg as usize].1 = in_use;
|
||||
// }
|
||||
|
||||
/// Free a temporary register after use
|
||||
/// NOTE: This will NOT free registers that contain variables!
|
||||
/// Variables persist throughout their scope and must not be freed
|
||||
pub fn free_temp(&mut self, reg: Register) {
|
||||
// Check if this register contains a variable
|
||||
if self.register_contents.contains_key(®) {
|
||||
// This register holds a variable - don't free it!
|
||||
// Variables are only freed when they go out of scope via free_var()
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a true temporary - safe to free
|
||||
if !matches!(reg, Register::Zero | Register::Null) {
|
||||
self.in_use[reg as usize].1 = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free_var(&mut self, var: &str) {
|
||||
// Check if this variable is in a register
|
||||
if let Some(location) = self.variable_locations.get(var).cloned() {
|
||||
if let Some(reg) = location.register
|
||||
&& !matches!(reg, Register::Zero | Register::Null)
|
||||
{
|
||||
self.register_contents.remove(®);
|
||||
self.in_use[reg as usize].1 = false;
|
||||
}
|
||||
|
||||
self.variable_locations.remove(var);
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate a register for a named variable
|
||||
/// Returns the register and any necessary assembly code
|
||||
pub fn alloc_var(
|
||||
&mut self,
|
||||
var_name: &str,
|
||||
) -> Result<(Register, InsBlock), CompilerError> {
|
||||
if let Some(mut location) = self.variable_locations.get(var_name).cloned() {
|
||||
// if the var is in a register we can use it already.
|
||||
if let Some(reg) = location.register {
|
||||
return Ok((reg, InsBlock::new()));
|
||||
}
|
||||
|
||||
// if the variable is on the stack only, we need to get it in a register.
|
||||
if let Some(offset) = location.stack {
|
||||
// Variable was pushed, need to calculate actual position and update its
|
||||
// location.
|
||||
let (reg, mut code) = self.alloc_temp()?;
|
||||
|
||||
// acknowledge var is now in a reg as well.
|
||||
location.register = Some(reg);
|
||||
|
||||
// Load from bpr + offset (offset is negative)
|
||||
// code.push(format!("\tsubi bpr {} {}", -(offset + 4), reg));
|
||||
|
||||
code.push(Instruction::ldw_reg_offset(
|
||||
Register::Spr,
|
||||
reg,
|
||||
offset - self.stack_offset,
|
||||
));
|
||||
|
||||
// Update location to register
|
||||
self.variable_locations
|
||||
.insert(var_name.to_string(), location);
|
||||
self.register_contents.insert(reg, var_name.to_string());
|
||||
|
||||
return Ok((reg, code));
|
||||
}
|
||||
}
|
||||
|
||||
// Variable doesn't have a location yet, allocate a new register
|
||||
let (reg, code) = self.alloc_temp()?;
|
||||
self.variable_locations
|
||||
.insert(var_name.to_string(), Location::register(reg));
|
||||
self.register_contents.insert(reg, var_name.to_string());
|
||||
|
||||
Ok((reg, code))
|
||||
}
|
||||
|
||||
/// Get the current location of a variable
|
||||
pub fn _get_var_location(&self, var_name: &str) -> Option<&Location> {
|
||||
self.variable_locations.get(var_name)
|
||||
}
|
||||
|
||||
/// Load a variable into a register (allocating if necessary)
|
||||
/// Returns the register and assembly code to load it
|
||||
pub fn load_var(
|
||||
&mut self,
|
||||
var_name: &str,
|
||||
) -> Result<(Register, InsBlock), CompilerError> {
|
||||
self.alloc_var(var_name)
|
||||
}
|
||||
|
||||
/// Store a value from a register into a variable
|
||||
/// Updates tracking and returns any necessary assembly code
|
||||
pub fn store_var(&mut self, var_name: &str, source_reg: &Register) -> InsBlock {
|
||||
let mut block = InsBlock::new();
|
||||
|
||||
// Check if variable already has a location
|
||||
if let Some(location) = self.variable_locations.get(var_name) {
|
||||
// if the variable exists in a register we write to that.
|
||||
match location.register {
|
||||
Some(reg) if reg == *source_reg => {
|
||||
block.push(Instruction::mov(*source_reg, reg));
|
||||
return block;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// if the variable exists on the stack but not a register we write here.
|
||||
if let Some(offset) = location.stack {
|
||||
block.push(Instruction::stw_reg_offset(
|
||||
*source_reg,
|
||||
Register::Spr,
|
||||
offset - self.stack_offset,
|
||||
));
|
||||
return block;
|
||||
}
|
||||
}
|
||||
|
||||
// Variable doesn't exist yet, we can just use the same reg.
|
||||
// if we can avoid a move, absolutely do that.
|
||||
|
||||
// if this is true then there's no permanent variable here so it's safe to use.
|
||||
if !self.register_contents.contains_key(source_reg) {
|
||||
self.variable_locations
|
||||
.insert(var_name.to_string(), Location::register(*source_reg));
|
||||
self.register_contents
|
||||
.insert(*source_reg, var_name.to_string());
|
||||
self.in_use[*source_reg as usize].1 = true;
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
// if current register isn't free, (eg is another variable) we assign somewhere
|
||||
// else.
|
||||
if let Some(free_reg) = self.find_free_register() {
|
||||
self.variable_locations
|
||||
.insert(var_name.to_string(), Location::register(free_reg));
|
||||
self.register_contents
|
||||
.insert(free_reg, var_name.to_string());
|
||||
self.in_use[free_reg as usize].1 = true;
|
||||
|
||||
block.push(Instruction::mov(*source_reg, free_reg));
|
||||
return block;
|
||||
}
|
||||
|
||||
// No free registers - allocate on stack
|
||||
// code.push(format!("\tstw {}, bpr, {}", source_reg, self.stack_offset));
|
||||
// self.variable_locations
|
||||
// .insert(var_name.to_string(), Location::Stack(self.stack_offset));
|
||||
// self.stack_offset -= 4; // Move to next stack slot
|
||||
//
|
||||
todo!("an efficient stack spilling algorithm. needs scope awareness.");
|
||||
}
|
||||
|
||||
/// spill a register to the stack (WITHOUT FREEING)
|
||||
/// DO NOT USE this if it's for a pointer!!!!
|
||||
pub fn _spill_register(&mut self, reg: &Register) -> Result<InsBlock, CompilerError> {
|
||||
let mut code = InsBlock::new();
|
||||
|
||||
// check if the variable is declared.
|
||||
if let Some(var_name) = self.register_contents.get(reg).cloned()
|
||||
&& let Some(location) = self.variable_locations.get_mut(&var_name)
|
||||
{
|
||||
// check if var is on the stack
|
||||
if let Some(offset) = location.stack {
|
||||
code.push(Instruction::stw_reg_offset(
|
||||
*reg,
|
||||
Register::Spr,
|
||||
offset - self.stack_offset,
|
||||
));
|
||||
return Ok(code);
|
||||
}
|
||||
|
||||
// Track that we pushed one word
|
||||
self.stack_offset -= 4;
|
||||
|
||||
// if the variable is not on the stack:
|
||||
// push register to stack (spr decrements automatically)
|
||||
let offset = self.stack_offset;
|
||||
code.push(Instruction::push(*reg));
|
||||
|
||||
// Update variable location - it's now at current spr
|
||||
// Note: We track offset from bpr for consistency
|
||||
location.stack = Some(offset);
|
||||
|
||||
Ok(code)
|
||||
} else {
|
||||
Err(CompilerError::Generic(format!(
|
||||
"Register {} does not contain a variable to spill!",
|
||||
reg
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// free a register by spilling it to the stack.
|
||||
/// Returns assembly code to perform the spill
|
||||
pub fn free_register(
|
||||
&mut self,
|
||||
reg: &Register,
|
||||
) -> Result<(i32, Instruction), CompilerError> {
|
||||
// check if the variable is declared.
|
||||
if let Some(var_name) = self.register_contents.get(reg).cloned()
|
||||
&& let Some(location) = self.variable_locations.get_mut(&var_name)
|
||||
{
|
||||
// check if var name is on the stack
|
||||
if let Some(offset) = location.stack {
|
||||
// store current register value in stack location
|
||||
let code = Instruction::stw_reg_offset(
|
||||
*reg,
|
||||
Register::Spr,
|
||||
offset - self.stack_offset,
|
||||
);
|
||||
|
||||
// free the register.
|
||||
location.register = None;
|
||||
self.register_contents.remove(reg);
|
||||
return Ok((offset, code));
|
||||
}
|
||||
|
||||
// Track that we pushed one word
|
||||
self.stack_offset -= 4;
|
||||
|
||||
let offset = self.stack_offset;
|
||||
let code = Instruction::push(*reg);
|
||||
|
||||
// Update variable location
|
||||
// Note: We track offset from bpr for consistency
|
||||
location.stack = Some(offset);
|
||||
location.register = None;
|
||||
self.register_contents.remove(reg);
|
||||
|
||||
Ok((offset, code))
|
||||
} else {
|
||||
Err(CompilerError::Generic(format!(
|
||||
"Register {} does not contain a variable to spill!",
|
||||
reg
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Find a free register (not currently in use)
|
||||
fn find_free_register(&self) -> Option<Register> {
|
||||
self.in_use
|
||||
.iter()
|
||||
.filter(|(_, in_use)| !*in_use)
|
||||
.map(|(reg, _)| *reg)
|
||||
.next()
|
||||
}
|
||||
|
||||
/// Spill all registers to stack (useful before function calls)
|
||||
pub fn _spill_all(&mut self) -> InsBlock {
|
||||
let mut code = InsBlock::new();
|
||||
|
||||
let regs_to_spill: Vec<Register> =
|
||||
self.register_contents.keys().cloned().collect();
|
||||
|
||||
for reg in regs_to_spill {
|
||||
if let Ok(spill_code) = self.free_register(®) {
|
||||
code.push(spill_code.1);
|
||||
}
|
||||
}
|
||||
|
||||
code
|
||||
}
|
||||
|
||||
/// Get the total stack offset
|
||||
pub fn get_stack_offset(&self) -> i32 {
|
||||
self.stack_offset
|
||||
}
|
||||
|
||||
/// Get the total stack space needed for local variables
|
||||
pub fn _get_stack_size(&self) -> i32 {
|
||||
-self.stack_offset // Convert negative offset to positive size
|
||||
}
|
||||
|
||||
/// Reset allocator for a new function
|
||||
pub fn reset(&mut self) {
|
||||
self.variable_locations.clear();
|
||||
self.register_contents.clear();
|
||||
self.stack_offset = -4;
|
||||
self.in_use = vec![
|
||||
Register::Rg0,
|
||||
Register::Rg1,
|
||||
Register::Rg2,
|
||||
Register::Rg3,
|
||||
Register::Rg4,
|
||||
Register::Rg5,
|
||||
Register::Rg6,
|
||||
Register::Rg7,
|
||||
Register::Rg8,
|
||||
Register::Rg9,
|
||||
Register::Rga,
|
||||
Register::Rgb,
|
||||
Register::Rgc,
|
||||
Register::Rgd,
|
||||
Register::Rge,
|
||||
Register::Rgf,
|
||||
]
|
||||
.iter()
|
||||
.map(|®| (reg, false))
|
||||
.collect();
|
||||
}
|
||||
|
||||
/// Get list of registers that contain variables and are in use
|
||||
/// These need to be saved before function calls
|
||||
pub fn get_caller_saved_registers(&self) -> Vec<Register> {
|
||||
self.register_contents
|
||||
.iter()
|
||||
.filter(|(reg, _)| {
|
||||
self.in_use
|
||||
.get(**reg as usize)
|
||||
.unwrap_or(&(Register::Null, false))
|
||||
.1
|
||||
})
|
||||
.map(|(reg, _)| *reg)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Register {
|
||||
// general purpose
|
||||
Rg0 = 0,
|
||||
Rg1 = 1,
|
||||
Rg2 = 2,
|
||||
Rg3 = 3,
|
||||
Rg4 = 4,
|
||||
Rg5 = 5,
|
||||
Rg6 = 6,
|
||||
Rg7 = 7,
|
||||
Rg8 = 8,
|
||||
Rg9 = 9,
|
||||
Rga = 10,
|
||||
Rgb = 11,
|
||||
Rgc = 12,
|
||||
Rgd = 13,
|
||||
Rge = 14,
|
||||
Rgf = 15,
|
||||
|
||||
// special
|
||||
Bpr,
|
||||
Spr,
|
||||
Ret,
|
||||
Acc,
|
||||
|
||||
// read only
|
||||
Pcx,
|
||||
Zero,
|
||||
|
||||
// null
|
||||
Null,
|
||||
}
|
||||
|
||||
impl Register {
|
||||
pub fn get_gp() -> [Register; 16] {
|
||||
[
|
||||
Register::Rg0,
|
||||
Register::Rg1,
|
||||
Register::Rg2,
|
||||
Register::Rg3,
|
||||
Register::Rg4,
|
||||
Register::Rg5,
|
||||
Register::Rg6,
|
||||
Register::Rg7,
|
||||
Register::Rg8,
|
||||
Register::Rg9,
|
||||
Register::Rga,
|
||||
Register::Rgb,
|
||||
Register::Rgc,
|
||||
Register::Rgd,
|
||||
Register::Rge,
|
||||
Register::Rgf,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn is_gp(&self) -> bool {
|
||||
(*self as u8) < 16
|
||||
}
|
||||
|
||||
pub fn from_index(idx: usize) -> Register {
|
||||
match idx {
|
||||
0 => Register::Rg0,
|
||||
1 => Register::Rg1,
|
||||
2 => Register::Rg2,
|
||||
3 => Register::Rg3,
|
||||
4 => Register::Rg4,
|
||||
5 => Register::Rg5,
|
||||
6 => Register::Rg6,
|
||||
7 => Register::Rg7,
|
||||
8 => Register::Rg8,
|
||||
9 => Register::Rg9,
|
||||
10 => Register::Rga,
|
||||
11 => Register::Rgb,
|
||||
12 => Register::Rgc,
|
||||
13 => Register::Rgd,
|
||||
14 => Register::Rge,
|
||||
15 => Register::Rgf,
|
||||
_ => unreachable!("this function shouldn't ever be called with idx>15"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Register {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Rg0 => write!(f, "rg0"),
|
||||
Self::Rg1 => write!(f, "rg1"),
|
||||
Self::Rg2 => write!(f, "rg2"),
|
||||
Self::Rg3 => write!(f, "rg3"),
|
||||
Self::Rg4 => write!(f, "rg4"),
|
||||
Self::Rg5 => write!(f, "rg5"),
|
||||
Self::Rg6 => write!(f, "rg6"),
|
||||
Self::Rg7 => write!(f, "rg7"),
|
||||
Self::Rg8 => write!(f, "rg8"),
|
||||
Self::Rg9 => write!(f, "rg9"),
|
||||
Self::Rga => write!(f, "rga"),
|
||||
Self::Rgb => write!(f, "rgb"),
|
||||
Self::Rgc => write!(f, "rgc"),
|
||||
Self::Rgd => write!(f, "rgd"),
|
||||
Self::Rge => write!(f, "rge"),
|
||||
Self::Rgf => write!(f, "rgf"),
|
||||
|
||||
Self::Acc => write!(f, "acc"),
|
||||
Self::Ret => write!(f, "ret"),
|
||||
Self::Bpr => write!(f, "bpr"),
|
||||
Self::Spr => write!(f, "spr"),
|
||||
|
||||
Self::Zero => write!(f, "zero"),
|
||||
Self::Pcx => write!(f, "pcx"),
|
||||
|
||||
Self::Null => write!(f, "null"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
use std::{cell::RefCell, collections::HashMap, ops::Deref, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
backend::dsa::{
|
||||
instruction::{InsBlock, Instruction},
|
||||
registers::{Register, RegisterAllocator},
|
||||
},
|
||||
error,
|
||||
model::{CompilerError, Name, TypeId},
|
||||
};
|
||||
|
||||
/// scope object
|
||||
pub struct Scope<'a> {
|
||||
/// outer scope, for a function this will be the global scope.
|
||||
parent: Option<&'a mut Scope<'a>>,
|
||||
alloc: Rc<RefCell<Allocator>>,
|
||||
|
||||
/// is the scope a function body or just a loop?
|
||||
/// depending on the type, ending a scope will have different behaviour
|
||||
r#type: ScopeType,
|
||||
|
||||
/// variables
|
||||
variables: HashMap<String, Variable>,
|
||||
|
||||
entry_stack_offset: i32,
|
||||
}
|
||||
|
||||
impl<'a> Scope<'a> {
|
||||
pub fn new() -> Scope<'a> {
|
||||
let alloc = Rc::new(RefCell::new(Allocator::new()));
|
||||
let entry_stack_offset = alloc.borrow().get_stack_offset();
|
||||
|
||||
Self {
|
||||
alloc,
|
||||
entry_stack_offset,
|
||||
parent: None,
|
||||
r#type: ScopeType::Function,
|
||||
variables: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from(parent: &'a mut Scope<'a>, r#type: ScopeType) -> Scope<'a> {
|
||||
let alloc = Rc::clone(&parent.alloc);
|
||||
let entry_stack_offset = alloc.borrow().get_stack_offset();
|
||||
|
||||
Self {
|
||||
alloc,
|
||||
entry_stack_offset,
|
||||
parent: Some(parent),
|
||||
r#type,
|
||||
variables: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_var(
|
||||
&mut self,
|
||||
name: String,
|
||||
r#type: TypeId,
|
||||
) -> Result<(), CompilerError> {
|
||||
let mut var = Variable::new(name, r#type.clone());
|
||||
|
||||
if r#type.size() > 4 {
|
||||
let slot = self.alloc.borrow_mut().allocate_stack_slot(r#type.size());
|
||||
var.stack_slot = Some(slot);
|
||||
} else {
|
||||
let reg = self.alloc.borrow_mut().allocate_var()?;
|
||||
var.register = Some(reg);
|
||||
}
|
||||
|
||||
self.variables.insert(var.name.clone(), var);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn alloc_temp(&mut self) -> Result<TempReg, CompilerError> {
|
||||
self.alloc.borrow_mut().allocate_temp()
|
||||
}
|
||||
|
||||
pub fn free_temp(&mut self, temp: &TempReg) {
|
||||
self.alloc.borrow_mut().free_temp(temp)
|
||||
}
|
||||
|
||||
pub fn free_var(&mut self, reg: &AssignedReg) {
|
||||
self.alloc.borrow_mut().free_var(reg);
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {
|
||||
// tell the allocator that this scope is closing
|
||||
// this reverts the stack offset to what it was before this scope was created.
|
||||
self.alloc.clone().borrow_mut().destroy_scope(self);
|
||||
|
||||
for var in self.variables.clone().values() {
|
||||
if let Some(reg) = var.register {
|
||||
self.free_var(®);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_var(&mut self, var: &str) -> Option<&mut Variable> {
|
||||
self.variables.get_mut(var)
|
||||
}
|
||||
|
||||
pub fn offset_read(
|
||||
&mut self,
|
||||
var: &str,
|
||||
offset: i32,
|
||||
) -> Result<(TempReg, Instruction), CompilerError> {
|
||||
if let Some(var) = self.get_var(var) {
|
||||
let slot = var.stack_slot.ok_or_else(|| {
|
||||
error("Attempt to read from a var without a stack slot!")
|
||||
})?;
|
||||
|
||||
return self.alloc.borrow_mut().offset_read(&slot, offset);
|
||||
}
|
||||
|
||||
Err(CompilerError::Undefined(Name::new(var, None)))
|
||||
}
|
||||
|
||||
pub fn offset_write(
|
||||
&mut self,
|
||||
reg: &TempReg,
|
||||
var: &str,
|
||||
offset: i32,
|
||||
) -> Result<Instruction, CompilerError> {
|
||||
if let Some(var) = self.get_var(var) {
|
||||
let slot = var.stack_slot.ok_or_else(|| {
|
||||
error("Attempt to write to a var without a stack slot!")
|
||||
})?;
|
||||
|
||||
return Ok(self.alloc.borrow_mut().offset_write(reg, &slot, offset));
|
||||
}
|
||||
|
||||
Err(CompilerError::Undefined(Name::new(var, None)))
|
||||
}
|
||||
|
||||
pub fn load_var(
|
||||
&mut self,
|
||||
var: &str,
|
||||
) -> Result<(AssignedReg, Instruction), CompilerError> {
|
||||
if let Some(v) = self.get_var(var).cloned()
|
||||
&& let Some(slot) = v.stack_slot
|
||||
{
|
||||
let res = self.alloc.borrow_mut().load_var(&slot)?;
|
||||
self.get_var(var).unwrap().register = Some(res.0);
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
panic!("e")
|
||||
}
|
||||
|
||||
pub fn spill_var(&mut self, var: &str) -> Result<Instruction, CompilerError> {
|
||||
if let Some(v) = self.get_var(var).cloned()
|
||||
&& let Some(rg) = v.register
|
||||
{
|
||||
let mut slot = v.stack_slot;
|
||||
let res = self.alloc.borrow_mut().spill_var(&rg, &mut slot);
|
||||
self.get_var(var).unwrap().stack_slot = slot;
|
||||
return res;
|
||||
}
|
||||
|
||||
Err(CompilerError::Undefined(Name::new(var, None)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Scope<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.close()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, Debug)]
|
||||
pub enum ScopeType {
|
||||
Function,
|
||||
IfBlock,
|
||||
LoopBlock,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Variable {
|
||||
pub name: String,
|
||||
|
||||
/// the type of the variable.
|
||||
r#type: TypeId,
|
||||
|
||||
/// size taken up in bytes.
|
||||
/// if size > 4, value must be stored on the stack.
|
||||
pub size: usize,
|
||||
|
||||
pub stack_slot: Option<StackSlot>,
|
||||
pub register: Option<AssignedReg>,
|
||||
}
|
||||
|
||||
impl Variable {
|
||||
pub fn new(name: String, r#type: TypeId) -> Self {
|
||||
Self {
|
||||
name,
|
||||
size: r#type.size(),
|
||||
r#type,
|
||||
stack_slot: None,
|
||||
register: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Allocator {
|
||||
stack_offset: i32,
|
||||
in_use: [(Register, bool); 16],
|
||||
}
|
||||
|
||||
impl Allocator {
|
||||
pub fn new() -> Self {
|
||||
let mut in_use = [(Register::Null, false); 16];
|
||||
in_use.copy_from_slice(&Register::get_gp().map(|r| (r, false))[0..16]);
|
||||
|
||||
Self {
|
||||
stack_offset: 0,
|
||||
in_use,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_stack_offset(&self) -> i32 {
|
||||
self.stack_offset
|
||||
}
|
||||
|
||||
pub fn destroy_scope(&mut self, scope: &mut Scope) {
|
||||
self.stack_offset = scope.entry_stack_offset;
|
||||
|
||||
for var in scope.variables.drain() {
|
||||
if let Some(assigned) = var.1.register {
|
||||
self.free_var(&assigned);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// what we need:
|
||||
|
||||
// - create var in register from temporary register. free temp and use it.
|
||||
//
|
||||
// - create var on stack from struct/array literal. return stack offset to write to.
|
||||
//
|
||||
// - spill var from register to stack. return stack offset to write to.
|
||||
//
|
||||
// - read/write var from stack+offset into register to use while preserving the stack
|
||||
// slot.
|
||||
//
|
||||
// - read / write bytes from the stack+offset in a larger variable into a register.
|
||||
|
||||
pub fn offset_read(
|
||||
&mut self,
|
||||
slot: &StackSlot,
|
||||
offset: i32,
|
||||
) -> Result<(TempReg, Instruction), CompilerError> {
|
||||
let register = self.allocate_temp()?;
|
||||
|
||||
// instruction: reg = *(&var + offset)
|
||||
Ok((
|
||||
register.clone(),
|
||||
Instruction::ldw_reg_offset(
|
||||
Register::Spr,
|
||||
*register,
|
||||
(**slot + offset) - self.stack_offset,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn offset_write(
|
||||
&mut self,
|
||||
reg: &TempReg,
|
||||
slot: &StackSlot,
|
||||
offset: i32,
|
||||
) -> Instruction {
|
||||
// instruction: *(&var + offset) = reg
|
||||
Instruction::stw_reg_offset(
|
||||
**reg,
|
||||
Register::Spr,
|
||||
(**slot + offset) - self.stack_offset,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn load_var(
|
||||
&mut self,
|
||||
slot: &StackSlot,
|
||||
) -> Result<(AssignedReg, Instruction), CompilerError> {
|
||||
let reg = self.allocate_var()?;
|
||||
|
||||
Ok((
|
||||
reg.clone(),
|
||||
Instruction::ldw_reg_offset(Register::Spr, *reg, **slot - self.stack_offset),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn spill_var(
|
||||
&mut self,
|
||||
reg: &AssignedReg,
|
||||
slot: &mut Option<StackSlot>,
|
||||
// var: &mut Variable,
|
||||
) -> Result<Instruction, CompilerError> {
|
||||
if let Some(slot) = &slot {
|
||||
let block = Instruction::stw_reg_offset(
|
||||
**reg,
|
||||
Register::Spr,
|
||||
**slot - self.stack_offset,
|
||||
);
|
||||
|
||||
self.free_var(reg);
|
||||
return Ok(block);
|
||||
}
|
||||
|
||||
// var doesn't have a stack slot so we need to create one
|
||||
let new_slot = self.allocate_stack_slot(4); // alloc 4 bytes for reg value.
|
||||
let block = Instruction::push(**reg);
|
||||
|
||||
self.free_var(reg);
|
||||
*slot = Some(new_slot);
|
||||
Ok(block)
|
||||
}
|
||||
|
||||
pub fn allocate_stack_slot(&mut self, size: usize) -> StackSlot {
|
||||
self.stack_offset -= size as i32;
|
||||
let offset = self.stack_offset;
|
||||
StackSlot(offset)
|
||||
}
|
||||
|
||||
pub fn allocate_var(&mut self) -> Result<AssignedReg, CompilerError> {
|
||||
if let Some(reg) = self.find_free_register() {
|
||||
Ok(AssignedReg(reg))
|
||||
} else {
|
||||
Err(CompilerError::Generic(
|
||||
"No free registers available".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocate_temp(&mut self) -> Result<TempReg, CompilerError> {
|
||||
// allocates a temporary register
|
||||
if let Some(reg) = self.find_free_register() {
|
||||
Ok(TempReg(reg))
|
||||
} else {
|
||||
todo!("an efficient stack spilling algorithm. needs scope awareness.");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free_temp(&mut self, temp: &TempReg) {
|
||||
// frees a temporary register.
|
||||
self.in_use[**temp as usize].1 = false;
|
||||
}
|
||||
|
||||
pub fn free_var(&mut self, reg: &AssignedReg) {
|
||||
// frees a register.
|
||||
self.in_use[**reg as usize].1 = false;
|
||||
}
|
||||
|
||||
// if we have register(s) free, return the first one.
|
||||
fn find_free_register(&mut self) -> Option<Register> {
|
||||
self.in_use.iter_mut().find_map(|(reg, used)| {
|
||||
if !*used {
|
||||
*used = true;
|
||||
Some(*reg)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TempReg(Register);
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct AssignedReg(Register);
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct StackSlot(i32);
|
||||
|
||||
impl Deref for TempReg {
|
||||
type Target = Register;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for AssignedReg {
|
||||
type Target = Register;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for StackSlot {
|
||||
type Target = i32;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use crate::model::{CompilerError, Program};
|
||||
|
||||
mod dsa;
|
||||
|
||||
pub fn compiler_backend(ext: &str, ast: &Program) -> Result<String, CompilerError> {
|
||||
match ext {
|
||||
"dsa" => Ok(dsa::generate_code(ast)?),
|
||||
_ => Err(CompilerError::Generic(format!(
|
||||
"File type {} not supported",
|
||||
ext
|
||||
))),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
// ============================================================================
|
||||
// Token Types
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TokenType {
|
||||
// Keywords
|
||||
Int,
|
||||
If,
|
||||
Else,
|
||||
While,
|
||||
Return,
|
||||
Include,
|
||||
|
||||
// Identifiers and literals
|
||||
Identifier(String),
|
||||
Number(i32),
|
||||
String(String),
|
||||
Char(char),
|
||||
|
||||
// Operators
|
||||
Plus,
|
||||
Minus,
|
||||
Star,
|
||||
Slash,
|
||||
Assign,
|
||||
Eq,
|
||||
Ne,
|
||||
Lt,
|
||||
Gt,
|
||||
Le,
|
||||
Ge,
|
||||
|
||||
// Delimiters
|
||||
LParen,
|
||||
RParen,
|
||||
LBrace,
|
||||
RBrace,
|
||||
Semicolon,
|
||||
Comma,
|
||||
Colon,
|
||||
Namespace,
|
||||
|
||||
Eof,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub enum Type {
|
||||
Int32,
|
||||
Int16,
|
||||
Int8,
|
||||
Uint32,
|
||||
Uint16,
|
||||
Uint8,
|
||||
Char,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Token {
|
||||
pub token_type: TokenType,
|
||||
pub line: usize,
|
||||
pub col: usize,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn new(token_type: TokenType, line: usize, col: usize) -> Self {
|
||||
Self {
|
||||
token_type,
|
||||
line,
|
||||
col,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Lexer
|
||||
// ============================================================================
|
||||
|
||||
pub struct Lexer {
|
||||
source: Vec<char>,
|
||||
pos: usize,
|
||||
line: usize,
|
||||
col: usize,
|
||||
}
|
||||
|
||||
impl Lexer {
|
||||
pub fn new(source: &str) -> Self {
|
||||
Self {
|
||||
source: source.chars().collect(),
|
||||
pos: 0,
|
||||
line: 1,
|
||||
col: 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn error(&self, msg: &str) -> String {
|
||||
format!(
|
||||
"Lexer error at line {}, col {}: {}",
|
||||
self.line, self.col, msg
|
||||
)
|
||||
}
|
||||
|
||||
fn peek(&self, offset: usize) -> Option<char> {
|
||||
self.source.get(self.pos + offset).copied()
|
||||
}
|
||||
|
||||
fn advance(&mut self) -> Option<char> {
|
||||
if self.pos >= self.source.len() {
|
||||
return None;
|
||||
}
|
||||
let ch = self.source[self.pos];
|
||||
self.pos += 1;
|
||||
if ch == '\n' {
|
||||
self.line += 1;
|
||||
self.col = 1;
|
||||
} else {
|
||||
self.col += 1;
|
||||
}
|
||||
Some(ch)
|
||||
}
|
||||
|
||||
fn skip_whitespace(&mut self) {
|
||||
while let Some(ch) = self.peek(0) {
|
||||
if ch.is_whitespace() {
|
||||
self.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_comment(&mut self) {
|
||||
if self.peek(0) == Some('/') && self.peek(1) == Some('/') {
|
||||
while let Some(ch) = self.peek(0) {
|
||||
if ch == '\n' {
|
||||
break;
|
||||
}
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_number(&mut self) -> i32 {
|
||||
let mut num_str = String::new();
|
||||
while let Some(ch) = self.peek(0) {
|
||||
if ch.is_ascii_digit() {
|
||||
num_str.push(ch);
|
||||
self.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
num_str.parse().unwrap_or(0)
|
||||
}
|
||||
|
||||
fn read_identifier(&mut self) -> String {
|
||||
let mut ident = String::new();
|
||||
while let Some(ch) = self.peek(0) {
|
||||
if ch.is_alphanumeric() || ch == '_' {
|
||||
ident.push(ch);
|
||||
self.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ident
|
||||
}
|
||||
|
||||
fn read_string(&mut self) -> Result<String, String> {
|
||||
let mut string = String::new();
|
||||
self.advance(); // Consume the opening quote
|
||||
|
||||
while let Some(ch) = self.peek(0) {
|
||||
if ch == '"' {
|
||||
self.advance(); // Consume the closing quote
|
||||
return Ok(string);
|
||||
} else if ch == '\\' {
|
||||
self.advance(); // Consume the backslash
|
||||
if let Some(escaped_char) = self.peek(0) {
|
||||
string.push(escaped_char);
|
||||
self.advance();
|
||||
}
|
||||
} else {
|
||||
string.push(ch);
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
Err(String::from("Unexpected EOF"))
|
||||
}
|
||||
|
||||
fn read_char(&mut self) -> Result<char, String> {
|
||||
self.advance(); // Consume the opening quote
|
||||
|
||||
if let Some(ch) = self.peek(0) {
|
||||
self.advance();
|
||||
if self.peek(0) == Some('\'') {
|
||||
self.advance();
|
||||
return Ok(ch);
|
||||
} else {
|
||||
Err(String::from("expected closing quote"))
|
||||
}
|
||||
} else {
|
||||
Err(String::from("expected character"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tokenize(&mut self) -> Result<Vec<Token>, String> {
|
||||
let mut tokens = Vec::new();
|
||||
|
||||
loop {
|
||||
self.skip_whitespace();
|
||||
self.skip_comment();
|
||||
|
||||
if self.pos >= self.source.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
let line = self.line;
|
||||
let col = self.col;
|
||||
let ch = self.peek(0).unwrap();
|
||||
|
||||
let token_type = if ch.is_ascii_digit() {
|
||||
let num = self.read_number();
|
||||
TokenType::Number(num)
|
||||
} else if ch == '"' {
|
||||
let string = self.read_string()?;
|
||||
TokenType::String(string)
|
||||
} else if ch == '\'' {
|
||||
let char = self.read_char()?;
|
||||
TokenType::Char(char)
|
||||
} else if ch.is_alphabetic() || ch == '_' {
|
||||
let ident = self.read_identifier();
|
||||
match ident.as_str() {
|
||||
"int" => TokenType::Int,
|
||||
"if" => TokenType::If,
|
||||
"else" => TokenType::Else,
|
||||
"while" => TokenType::While,
|
||||
"return" => TokenType::Return,
|
||||
"include" => TokenType::Include,
|
||||
_ => TokenType::Identifier(ident),
|
||||
}
|
||||
} else {
|
||||
match ch {
|
||||
':' if self.peek(1) == Some(':') => {
|
||||
self.advance();
|
||||
self.advance();
|
||||
TokenType::Namespace
|
||||
}
|
||||
':' => {
|
||||
self.advance();
|
||||
TokenType::Colon
|
||||
}
|
||||
'=' if self.peek(1) == Some('=') => {
|
||||
self.advance();
|
||||
self.advance();
|
||||
TokenType::Eq
|
||||
}
|
||||
'!' if self.peek(1) == Some('=') => {
|
||||
self.advance();
|
||||
self.advance();
|
||||
TokenType::Ne
|
||||
}
|
||||
'<' if self.peek(1) == Some('=') => {
|
||||
self.advance();
|
||||
self.advance();
|
||||
TokenType::Le
|
||||
}
|
||||
'>' if self.peek(1) == Some('=') => {
|
||||
self.advance();
|
||||
self.advance();
|
||||
TokenType::Ge
|
||||
}
|
||||
'+' => {
|
||||
self.advance();
|
||||
TokenType::Plus
|
||||
}
|
||||
'-' => {
|
||||
self.advance();
|
||||
TokenType::Minus
|
||||
}
|
||||
'*' => {
|
||||
self.advance();
|
||||
TokenType::Star
|
||||
}
|
||||
'/' => {
|
||||
self.advance();
|
||||
TokenType::Slash
|
||||
}
|
||||
'=' => {
|
||||
self.advance();
|
||||
TokenType::Assign
|
||||
}
|
||||
'<' => {
|
||||
self.advance();
|
||||
TokenType::Lt
|
||||
}
|
||||
'>' => {
|
||||
self.advance();
|
||||
TokenType::Gt
|
||||
}
|
||||
'(' => {
|
||||
self.advance();
|
||||
TokenType::LParen
|
||||
}
|
||||
')' => {
|
||||
self.advance();
|
||||
TokenType::RParen
|
||||
}
|
||||
'{' => {
|
||||
self.advance();
|
||||
TokenType::LBrace
|
||||
}
|
||||
'}' => {
|
||||
self.advance();
|
||||
TokenType::RBrace
|
||||
}
|
||||
';' => {
|
||||
self.advance();
|
||||
TokenType::Semicolon
|
||||
}
|
||||
',' => {
|
||||
self.advance();
|
||||
TokenType::Comma
|
||||
}
|
||||
_ => return Err(self.error(&format!("Unexpected character: {}", ch))),
|
||||
}
|
||||
};
|
||||
|
||||
tokens.push(Token::new(token_type, line, col));
|
||||
}
|
||||
|
||||
tokens.push(Token::new(TokenType::Eof, self.line, self.col));
|
||||
Ok(tokens)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
use common::logging::log;
|
||||
|
||||
use crate::model::{CompilerError, Program};
|
||||
use parser::Parser;
|
||||
|
||||
pub mod lexer;
|
||||
pub mod parser;
|
||||
|
||||
pub fn generate_ast(input: &str) -> Result<Program, CompilerError> {
|
||||
log("Tokenising Input...");
|
||||
|
||||
let mut lexer = lexer::Lexer::new(&input);
|
||||
let tokens = lexer.tokenize().map_err(|e| CompilerError::Generic(e))?;
|
||||
// println!("{tokens:?}");
|
||||
|
||||
log(&format!("Parsing {} Tokens...", tokens.len()));
|
||||
|
||||
let mut parser = Parser::new(tokens);
|
||||
let ast = match parser.parse() {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => return Err(CompilerError::Generic(e)),
|
||||
};
|
||||
|
||||
Ok(ast)
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
// ============================================================================
|
||||
// AST Node Types
|
||||
// ============================================================================
|
||||
|
||||
use crate::model::{
|
||||
BinaryOperator, Block, ConstExpr, Declaration, Dependency, Expression, Name, Program,
|
||||
Statement, TypeId, UnaryOperator, Variable,
|
||||
};
|
||||
|
||||
use super::lexer::{Token, TokenType};
|
||||
|
||||
// ============================================================================
|
||||
// Parser
|
||||
// ============================================================================
|
||||
|
||||
pub struct Parser {
|
||||
tokens: Vec<Token>,
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new(tokens: Vec<Token>) -> Self {
|
||||
Self { tokens, pos: 0 }
|
||||
}
|
||||
|
||||
fn error(&self, msg: &str) -> String {
|
||||
let token = self.current();
|
||||
format!(
|
||||
"Parser error at line {}, col {}: {}",
|
||||
token.line, token.col, msg
|
||||
)
|
||||
}
|
||||
|
||||
fn current(&self) -> &Token {
|
||||
self.tokens
|
||||
.get(self.pos)
|
||||
.unwrap_or_else(|| self.tokens.last().unwrap())
|
||||
}
|
||||
|
||||
fn peek(&self, offset: usize) -> &Token {
|
||||
self.tokens
|
||||
.get(self.pos + offset)
|
||||
.unwrap_or_else(|| self.tokens.last().unwrap())
|
||||
}
|
||||
|
||||
fn advance(&mut self) -> &Token {
|
||||
if self.pos < self.tokens.len() - 1 {
|
||||
self.pos += 1;
|
||||
}
|
||||
self.current()
|
||||
}
|
||||
|
||||
fn expect(&mut self, expected: TokenType) -> Result<Token, String> {
|
||||
let token = self.current().clone();
|
||||
if std::mem::discriminant(&token.token_type) != std::mem::discriminant(&expected)
|
||||
{
|
||||
return Err(self.error(&format!(
|
||||
"Expected {:?}, got {:?}",
|
||||
expected, token.token_type
|
||||
)));
|
||||
}
|
||||
self.advance();
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
pub fn parse(&mut self) -> Result<Program, String> {
|
||||
let mut declarations = Vec::new();
|
||||
|
||||
while !matches!(self.current().token_type, TokenType::Eof) {
|
||||
declarations.push(self.parse_declaration()?);
|
||||
}
|
||||
|
||||
Ok(Program { declarations })
|
||||
}
|
||||
|
||||
fn parse_declaration(&mut self) -> Result<Declaration, String> {
|
||||
// check for an import
|
||||
if let TokenType::Include = self.current().token_type {
|
||||
self.advance();
|
||||
|
||||
let name =
|
||||
if let TokenType::Identifier(id) = self.current().clone().token_type {
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.ok_or(String::from("Expected identifier"))?;
|
||||
|
||||
self.advance();
|
||||
self.expect(TokenType::Colon)?;
|
||||
|
||||
let path = if let TokenType::String(id) = self.current().clone().token_type {
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.ok_or(String::from("Expected string literal"))?;
|
||||
|
||||
self.advance();
|
||||
return Ok(Declaration::Dependency(Dependency { name, path }));
|
||||
}
|
||||
|
||||
self.expect(TokenType::Int)?;
|
||||
|
||||
let name = match &self.current().token_type {
|
||||
TokenType::Identifier(s) => s.clone(),
|
||||
_ => return Err(self.error("Expected identifier")),
|
||||
};
|
||||
self.advance();
|
||||
|
||||
match &self.current().token_type {
|
||||
TokenType::LParen => {
|
||||
// Function declaration
|
||||
self.advance();
|
||||
let mut params = Vec::<Variable>::new();
|
||||
|
||||
if !matches!(self.current().token_type, TokenType::RParen) {
|
||||
self.expect(TokenType::Int)?;
|
||||
|
||||
match &self.current().token_type {
|
||||
TokenType::Identifier(s) => {
|
||||
params.push(Variable {
|
||||
name: s.clone(),
|
||||
type_id: TypeId::U32,
|
||||
});
|
||||
self.advance();
|
||||
}
|
||||
_ => return Err(self.error("Expected parameter name")),
|
||||
}
|
||||
|
||||
while matches!(self.current().token_type, TokenType::Comma) {
|
||||
self.advance();
|
||||
self.expect(TokenType::Int)?;
|
||||
|
||||
match &self.current().token_type {
|
||||
TokenType::Identifier(s) => {
|
||||
params.push(Variable {
|
||||
name: s.clone(),
|
||||
type_id: TypeId::U32,
|
||||
});
|
||||
self.advance();
|
||||
}
|
||||
_ => return Err(self.error("Expected parameter name")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.expect(TokenType::RParen)?;
|
||||
let body = self.parse_block()?;
|
||||
|
||||
Ok(Declaration::Function {
|
||||
name,
|
||||
params,
|
||||
body,
|
||||
return_type: TypeId::U32,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
// Variable declaration
|
||||
let init = if matches!(self.current().token_type, TokenType::Assign) {
|
||||
self.advance();
|
||||
|
||||
if let TokenType::Number(n) = self.current().token_type {
|
||||
self.advance();
|
||||
Some(ConstExpr::Number(n))
|
||||
} else {
|
||||
return Err(self
|
||||
.error("Expected constant in global variable declaration"));
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.expect(TokenType::Semicolon)?;
|
||||
Ok(Declaration::Variable {
|
||||
var: Variable {
|
||||
name,
|
||||
type_id: TypeId::U32,
|
||||
},
|
||||
init,
|
||||
is_const: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_block(&mut self) -> Result<Block, String> {
|
||||
self.expect(TokenType::LBrace)?;
|
||||
let mut statements = Vec::new();
|
||||
|
||||
while !matches!(self.current().token_type, TokenType::RBrace) {
|
||||
statements.push(self.parse_statement()?);
|
||||
}
|
||||
|
||||
self.expect(TokenType::RBrace)?;
|
||||
Ok(statements)
|
||||
}
|
||||
|
||||
fn parse_statement(&mut self) -> Result<Statement, String> {
|
||||
match &self.current().token_type {
|
||||
TokenType::LBrace => Ok(Statement::Block(self.parse_block()?)),
|
||||
TokenType::If => self.parse_if_stmt(),
|
||||
TokenType::While => self.parse_while_stmt(),
|
||||
TokenType::Return => self.parse_return_stmt(),
|
||||
TokenType::Identifier(name) => {
|
||||
let name = name.clone();
|
||||
|
||||
// peek ahead for open paren (func call expr)
|
||||
if matches!(self.peek(1).token_type, TokenType::LParen) {
|
||||
let expr = self.parse_expression()?; // a function call expr
|
||||
self.expect(TokenType::Semicolon)?;
|
||||
return Ok(Statement::Expression { expr });
|
||||
}
|
||||
|
||||
self.advance(); // advance past identifier
|
||||
|
||||
// assignment expression
|
||||
if matches!(self.current().token_type, TokenType::Assign) {
|
||||
self.advance();
|
||||
let expr = self.parse_expression()?;
|
||||
|
||||
self.expect(TokenType::Semicolon)?;
|
||||
Ok(Statement::Assign {
|
||||
varname: name,
|
||||
value: expr,
|
||||
})
|
||||
}
|
||||
// var expression
|
||||
else {
|
||||
self.expect(TokenType::Semicolon)?;
|
||||
Ok(Statement::Expression {
|
||||
expr: Expression::Variable {
|
||||
name: Name {
|
||||
name,
|
||||
namespace: None,
|
||||
},
|
||||
expr_type: None,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
TokenType::Int => {
|
||||
// Local variable declaration
|
||||
self.advance();
|
||||
let name = match &self.current().token_type {
|
||||
TokenType::Identifier(s) => s.clone(),
|
||||
_ => return Err(self.error("Expected variable name")),
|
||||
};
|
||||
self.advance();
|
||||
|
||||
let init = if matches!(self.current().token_type, TokenType::Assign) {
|
||||
self.advance();
|
||||
Some(self.parse_expression()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.expect(TokenType::Semicolon)?;
|
||||
|
||||
// Convert to assignment expression statement
|
||||
let expr = if let Some(init_expr) = init {
|
||||
Statement::Assign {
|
||||
varname: name,
|
||||
value: init_expr,
|
||||
}
|
||||
} else {
|
||||
Statement::Assign {
|
||||
varname: name,
|
||||
value: Expression::Empty,
|
||||
}
|
||||
};
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
_ => {
|
||||
let expr = if matches!(self.current().token_type, TokenType::Semicolon) {
|
||||
Expression::Empty
|
||||
} else {
|
||||
self.parse_expression()?
|
||||
};
|
||||
|
||||
self.expect(TokenType::Semicolon)?;
|
||||
Ok(Statement::Expression { expr })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_if_stmt(&mut self) -> Result<Statement, String> {
|
||||
self.expect(TokenType::If)?;
|
||||
self.expect(TokenType::LParen)?;
|
||||
let condition = self.parse_expression()?;
|
||||
self.expect(TokenType::RParen)?;
|
||||
let then_stmt = self.parse_block()?;
|
||||
|
||||
let else_stmt = if matches!(self.current().token_type, TokenType::Else) {
|
||||
self.advance();
|
||||
self.parse_block()?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
Ok(Statement::If {
|
||||
condition,
|
||||
then_stmt,
|
||||
else_stmt,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_while_stmt(&mut self) -> Result<Statement, String> {
|
||||
self.expect(TokenType::While)?;
|
||||
self.expect(TokenType::LParen)?;
|
||||
let condition = self.parse_expression()?;
|
||||
self.expect(TokenType::RParen)?;
|
||||
let body = self.parse_block()?;
|
||||
|
||||
Ok(Statement::While { condition, body })
|
||||
}
|
||||
|
||||
fn parse_return_stmt(&mut self) -> Result<Statement, String> {
|
||||
self.expect(TokenType::Return)?;
|
||||
|
||||
let expr = if matches!(self.current().token_type, TokenType::Semicolon) {
|
||||
None
|
||||
} else {
|
||||
Some(self.parse_expression()?)
|
||||
};
|
||||
|
||||
self.expect(TokenType::Semicolon)?;
|
||||
Ok(Statement::Return(expr))
|
||||
}
|
||||
|
||||
fn parse_expression(&mut self) -> Result<Expression, String> {
|
||||
self.parse_comparison()
|
||||
}
|
||||
|
||||
fn parse_comparison(&mut self) -> Result<Expression, String> {
|
||||
let mut expr = self.parse_additive()?;
|
||||
|
||||
while let Some(op) = match &self.current().token_type {
|
||||
TokenType::Eq => Some(BinaryOperator::Eq),
|
||||
TokenType::Ne => Some(BinaryOperator::Ne),
|
||||
TokenType::Lt => Some(BinaryOperator::Lt),
|
||||
TokenType::Gt => Some(BinaryOperator::Gt),
|
||||
TokenType::Le => Some(BinaryOperator::Le),
|
||||
TokenType::Ge => Some(BinaryOperator::Ge),
|
||||
_ => None,
|
||||
} {
|
||||
self.advance();
|
||||
let right = Box::new(self.parse_additive()?);
|
||||
expr = Expression::Binary {
|
||||
op,
|
||||
left: Box::new(expr),
|
||||
right,
|
||||
type_id: None,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
fn parse_additive(&mut self) -> Result<Expression, String> {
|
||||
let mut expr = self.parse_multiplicative()?;
|
||||
|
||||
while let Some(op) = match &self.current().token_type {
|
||||
TokenType::Plus => Some(BinaryOperator::Add),
|
||||
TokenType::Minus => Some(BinaryOperator::Sub),
|
||||
_ => None,
|
||||
} {
|
||||
self.advance();
|
||||
let right = Box::new(self.parse_multiplicative()?);
|
||||
expr = Expression::Binary {
|
||||
op,
|
||||
left: Box::new(expr),
|
||||
right,
|
||||
type_id: None,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
fn parse_multiplicative(&mut self) -> Result<Expression, String> {
|
||||
let mut expr = self.parse_unary()?;
|
||||
|
||||
while let Some(op) = match &self.current().token_type {
|
||||
TokenType::Star => Some(BinaryOperator::Mul),
|
||||
TokenType::Slash => Some(BinaryOperator::Div),
|
||||
_ => None,
|
||||
} {
|
||||
self.advance();
|
||||
let right = Box::new(self.parse_unary()?);
|
||||
expr = Expression::Binary {
|
||||
op,
|
||||
left: Box::new(expr),
|
||||
right,
|
||||
type_id: None,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
fn parse_unary(&mut self) -> Result<Expression, String> {
|
||||
let op = match &self.current().token_type {
|
||||
TokenType::Plus => Some(UnaryOperator::Plus),
|
||||
TokenType::Minus => Some(UnaryOperator::Minus),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(op) = op {
|
||||
self.advance();
|
||||
let operand = Box::new(self.parse_unary()?);
|
||||
return Ok(Expression::Unary {
|
||||
op,
|
||||
operand,
|
||||
type_id: None,
|
||||
});
|
||||
}
|
||||
|
||||
self.parse_primary()
|
||||
}
|
||||
|
||||
fn parse_primary(&mut self) -> Result<Expression, String> {
|
||||
match &self.current().token_type.clone() {
|
||||
TokenType::Number(n) => {
|
||||
let value = *n;
|
||||
self.advance();
|
||||
Ok(Expression::Number {
|
||||
value: value as isize,
|
||||
type_id: None,
|
||||
})
|
||||
}
|
||||
TokenType::Identifier(name) => {
|
||||
let name = name.clone();
|
||||
self.advance();
|
||||
|
||||
if matches!(self.current().token_type, TokenType::LParen) {
|
||||
// Function call
|
||||
self.advance();
|
||||
let mut args = Vec::new();
|
||||
|
||||
if !matches!(self.current().token_type, TokenType::RParen) {
|
||||
args.push(self.parse_expression()?);
|
||||
|
||||
while matches!(self.current().token_type, TokenType::Comma) {
|
||||
self.advance();
|
||||
args.push(self.parse_expression()?);
|
||||
}
|
||||
}
|
||||
|
||||
self.expect(TokenType::RParen)?;
|
||||
Ok(Expression::Call {
|
||||
name: Name {
|
||||
name,
|
||||
namespace: None,
|
||||
},
|
||||
args,
|
||||
type_id: None,
|
||||
})
|
||||
} else {
|
||||
Ok(Expression::Variable {
|
||||
name: Name {
|
||||
name,
|
||||
namespace: None,
|
||||
},
|
||||
expr_type: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
TokenType::LParen => {
|
||||
self.advance();
|
||||
let expr = self.parse_expression()?;
|
||||
self.expect(TokenType::RParen)?;
|
||||
Ok(expr)
|
||||
}
|
||||
_ => Err(self.error(&format!(
|
||||
"Unexpected token: {:?}",
|
||||
self.current().token_type
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
use crate::model::{CompilerError, Program};
|
||||
use common::logging::Logger;
|
||||
use parser::{ParseResult, Parser};
|
||||
// use semantic_analyser::Analyser;
|
||||
|
||||
pub mod lexer;
|
||||
pub mod parser;
|
||||
// pub mod semantic_analyser;
|
||||
|
||||
pub fn generate_ast(input: &str, logger: &Logger) -> Result<Program, CompilerError> {
|
||||
logger.info("Tokenising Input...");
|
||||
|
||||
let lexer = lexer::Lexer::new(&input);
|
||||
let tokens = lexer.collect::<Vec<_>>();
|
||||
|
||||
// println!("{tokens:#?}");
|
||||
|
||||
logger.info(&format!("Parsing {} Tokens...", tokens.len()));
|
||||
|
||||
let mut parser = Parser::new(tokens);
|
||||
let ast = match parser.parse() {
|
||||
ParseResult::Accept(ast) => ast,
|
||||
ParseResult::Reject(e) => return Err(e),
|
||||
ParseResult::Deny => {
|
||||
return Err(CompilerError::Generic("Parser used ::Deny".to_string()));
|
||||
}
|
||||
};
|
||||
// println!("{ast:#?}");
|
||||
|
||||
logger.info("Analyzing AST...");
|
||||
logger.info("Checking Type Information...");
|
||||
|
||||
// let mut analyser = Analyser::new();
|
||||
// analyser.analyse(ast.clone()).unwrap();
|
||||
|
||||
logger.info("Type Checking Complete...");
|
||||
Ok(ast)
|
||||
}
|
||||
@@ -0,0 +1,985 @@
|
||||
use super::lexer::Token;
|
||||
use crate::model::{
|
||||
AssignmentOperator, BinaryOperator, Block, Call, CompilerError, ConstExpr,
|
||||
Declaration, Dependency, Expression, Number, Program, Statement, TypeId,
|
||||
UnaryOperator, Variable,
|
||||
};
|
||||
use crate::{expect_tt, expect_value};
|
||||
use std::ops::{ControlFlow, FromResidual, Try};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ParseResult<T, E> {
|
||||
Accept(T),
|
||||
Deny,
|
||||
Reject(E),
|
||||
}
|
||||
|
||||
pub struct Parser {
|
||||
tokens: Vec<Token>,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new(tokens: Vec<Token>) -> Self {
|
||||
Self { tokens, idx: 0 }
|
||||
}
|
||||
|
||||
pub fn parse(&mut self) -> ParseResult<Program, CompilerError> {
|
||||
let mut declarations = Vec::new();
|
||||
|
||||
while let ParseResult::Accept(_) = self.peek_next() {
|
||||
declarations.push(self.parse_declaration()?);
|
||||
}
|
||||
|
||||
ParseResult::Accept(Program { declarations })
|
||||
}
|
||||
|
||||
fn parse_declaration(&mut self) -> ParseResult<Declaration, CompilerError> {
|
||||
if expect_tt!(self.peek_next()?, Fn).accepted() {
|
||||
return self.parse_func();
|
||||
}
|
||||
|
||||
if expect_tt!(self.peek_next()?, Struct).accepted() {
|
||||
return self.parse_struct();
|
||||
}
|
||||
|
||||
if expect_tt!(self.peek_next()?, Include).accepted() {
|
||||
// expect include keyword
|
||||
let _ = self.next();
|
||||
|
||||
// expect namespace identifier
|
||||
let name = expect_value!(self.next()?, Identifier)?;
|
||||
|
||||
// expect colon
|
||||
let _ = expect_tt!(self.next()?, Colon)?;
|
||||
|
||||
// expect string literal (module path)
|
||||
let path = expect_value!(self.next()?, String)?;
|
||||
|
||||
// expect semicolon
|
||||
let _ = expect_tt!(self.next()?, Semicolon)?;
|
||||
|
||||
return ParseResult::Accept(Declaration::Dependency(Dependency {
|
||||
name: name.name,
|
||||
path,
|
||||
}));
|
||||
}
|
||||
|
||||
if expect_tt!(self.peek_next()?, Const, Static).accepted() {
|
||||
let is_const = match self.next()? {
|
||||
Token::Const => true,
|
||||
Token::Static => false,
|
||||
_ => {
|
||||
return ParseResult::Reject(CompilerError::Generic(String::from(
|
||||
"This can't happen!",
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let var = self.parse_var_decl()?;
|
||||
|
||||
let _ = expect_tt!(self.next()?, Assign)?;
|
||||
|
||||
let value = self.next()?;
|
||||
let init = match value {
|
||||
Token::String(x) => Some(ConstExpr::String(x)),
|
||||
Token::SignedInt(x, _) => Some(ConstExpr::Number(x)),
|
||||
Token::UnsignedInt(x, _) => Some(ConstExpr::Number(x as i32)),
|
||||
_ => {
|
||||
return ParseResult::Reject(CompilerError::UnexpectedToken(
|
||||
value.tt().to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let _ = expect_tt!(self.next()?, Semicolon)?;
|
||||
|
||||
return ParseResult::Accept(Declaration::Variable {
|
||||
var,
|
||||
init,
|
||||
is_const,
|
||||
});
|
||||
}
|
||||
|
||||
ParseResult::Reject(CompilerError::UnexpectedEndOfInput)
|
||||
}
|
||||
|
||||
fn parse_struct(&mut self) -> ParseResult<Declaration, CompilerError> {
|
||||
let _ = expect_tt!(self.next()?, Struct)?;
|
||||
let name = expect_value!(self.next()?, Identifier)?;
|
||||
|
||||
let _ = expect_tt!(self.next()?, LeftBrace)?;
|
||||
|
||||
let mut fields = Vec::new();
|
||||
while expect_tt!(self.peek_next()?, Identifier).accepted() {
|
||||
let arg = self.parse_var_decl()?;
|
||||
fields.push(arg);
|
||||
|
||||
if expect_tt!(self.peek_next()?, Comma).accepted() {
|
||||
self.next()?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let _ = expect_tt!(self.next()?, RightBrace)?;
|
||||
ParseResult::Accept(Declaration::Struct { name, fields })
|
||||
}
|
||||
|
||||
fn parse_func(&mut self) -> ParseResult<Declaration, CompilerError> {
|
||||
// expect function keyword
|
||||
let _ = expect_tt!(self.next()?, Fn);
|
||||
// expect function name
|
||||
let name = expect_value!(self.next()?, Identifier)?;
|
||||
|
||||
// expect left paren
|
||||
let _ = expect_tt!(self.next()?, LeftParen)?;
|
||||
|
||||
let mut params = Vec::new();
|
||||
while expect_tt!(self.peek_next()?, Identifier).accepted() {
|
||||
let arg = self.parse_var_decl()?;
|
||||
params.push(arg);
|
||||
|
||||
if expect_tt!(self.peek_next()?, Comma).accepted() {
|
||||
self.next()?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// expect right paren
|
||||
let _ = expect_tt!(self.next()?, RightParen)?;
|
||||
|
||||
// see if we can parse the return type!
|
||||
let mut return_type = TypeId::Void;
|
||||
if expect_tt!(self.peek_next()?, RightArrow).accepted() {
|
||||
let _ = self.next();
|
||||
return_type = self.parse_type()?;
|
||||
}
|
||||
|
||||
// expect vald block
|
||||
if expect_tt!(self.peek_next()?, LeftBrace).accepted() {
|
||||
ParseResult::Accept(Declaration::Function {
|
||||
name: name.name,
|
||||
params,
|
||||
return_type,
|
||||
body: self.parse_block()?,
|
||||
})
|
||||
} else {
|
||||
ParseResult::Reject(CompilerError::UnexpectedToken(
|
||||
self.peek_next()?.tt().to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_block(&mut self) -> ParseResult<Block, CompilerError> {
|
||||
// expect left brace
|
||||
let _ = expect_tt!(self.next()?, LeftBrace)?;
|
||||
|
||||
let mut block = Vec::new();
|
||||
while !expect_tt!(self.peek_next()?, RightBrace).accepted() {
|
||||
block.push(self.parse_statement()?);
|
||||
}
|
||||
|
||||
// expect right brace
|
||||
let _ = expect_tt!(self.next()?, RightBrace)?;
|
||||
|
||||
ParseResult::Accept(block)
|
||||
}
|
||||
|
||||
fn parse_statement(&mut self) -> ParseResult<Statement, CompilerError> {
|
||||
// handle if statements
|
||||
if expect_tt!(self.peek_next()?, If).accepted() {
|
||||
self.next()?;
|
||||
|
||||
let condition = self.parse_expression()?;
|
||||
|
||||
let then_stmt = self.parse_block()?;
|
||||
|
||||
if !expect_tt!(self.peek_next()?, Else).accepted() {
|
||||
return ParseResult::Accept(Statement::If {
|
||||
condition,
|
||||
then_stmt,
|
||||
else_stmt: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
let _ = expect_tt!(self.next()?, Else)?;
|
||||
|
||||
let else_stmt = self.parse_block()?;
|
||||
|
||||
return ParseResult::Accept(Statement::If {
|
||||
condition,
|
||||
then_stmt,
|
||||
else_stmt,
|
||||
});
|
||||
}
|
||||
|
||||
// handle while loops
|
||||
if expect_tt!(self.peek_next()?, While).accepted() {
|
||||
self.next()?;
|
||||
|
||||
// expect valid expression
|
||||
let expr = self.parse_expression()?;
|
||||
|
||||
// expect valid block after
|
||||
let block = self.parse_block()?;
|
||||
|
||||
// return result
|
||||
return ParseResult::Accept(Statement::While {
|
||||
condition: expr,
|
||||
body: block,
|
||||
});
|
||||
}
|
||||
|
||||
// handle indefinite loops
|
||||
if expect_tt!(self.peek_next()?, Loop).accepted() {
|
||||
self.next()?;
|
||||
|
||||
// parse the inner block
|
||||
return ParseResult::Accept(Statement::Loop(self.parse_block()?));
|
||||
}
|
||||
|
||||
if expect_tt!(self.peek_next()?, Return).accepted() {
|
||||
self.next()?;
|
||||
|
||||
// handle case where nothing is returned
|
||||
if expect_tt!(self.peek_next()?, Semicolon).accepted() {
|
||||
return ParseResult::Accept(Statement::Return(None));
|
||||
}
|
||||
|
||||
let expr = self.parse_expression()?;
|
||||
expect_tt!(self.next()?, Semicolon)?;
|
||||
|
||||
return ParseResult::Accept(Statement::Return(Some(expr)));
|
||||
}
|
||||
|
||||
if expect_tt!(self.peek_next()?, Break).accepted() {
|
||||
self.next()?;
|
||||
|
||||
// expect semicolon
|
||||
expect_tt!(self.next()?, Semicolon)?;
|
||||
|
||||
// return result
|
||||
return ParseResult::Accept(Statement::Break);
|
||||
}
|
||||
|
||||
if expect_tt!(self.peek_next()?, Continue).accepted() {
|
||||
self.next()?;
|
||||
|
||||
// expect semicolon
|
||||
expect_tt!(self.next()?, Semicolon)?;
|
||||
|
||||
// return result
|
||||
return ParseResult::Accept(Statement::Continue);
|
||||
}
|
||||
|
||||
// handle writes to pointers!
|
||||
if expect_tt!(self.peek_next()?, Star).accepted() {
|
||||
self.next()?;
|
||||
|
||||
let left = if expect_tt!(self.peek_next()?, Identifier).accepted() {
|
||||
let identifier = expect_value!(self.next()?, Identifier)?;
|
||||
|
||||
Expression::Variable {
|
||||
name: identifier,
|
||||
expr_type: None,
|
||||
}
|
||||
} else if expect_tt!(self.peek_next()?, LeftParen).accepted() {
|
||||
self.next()?;
|
||||
|
||||
let expr = self.parse_expression()?;
|
||||
|
||||
let _ = expect_tt!(self.next()?, RightParen).accepted();
|
||||
|
||||
expr
|
||||
} else {
|
||||
return ParseResult::Reject(CompilerError::UnexpectedToken(
|
||||
self.peek_next()?.tt().to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let _ = expect_tt!(self.next()?, Assign)?;
|
||||
|
||||
let right = self.parse_expression()?;
|
||||
|
||||
// expect semicolon
|
||||
expect_tt!(self.next()?, Semicolon)?;
|
||||
|
||||
// return result
|
||||
return ParseResult::Accept(Statement::PtrWrite {
|
||||
ptr: left,
|
||||
value: right,
|
||||
});
|
||||
}
|
||||
|
||||
// handle let statements (declarations)
|
||||
if expect_tt!(self.peek_next()?, Let).accepted() {
|
||||
self.next();
|
||||
|
||||
// expect variable name and type.
|
||||
let name = self.parse_var_decl()?;
|
||||
|
||||
// handle uninitialised variable case
|
||||
if expect_tt!(self.peek_next()?, Semicolon).accepted() {
|
||||
self.next();
|
||||
return ParseResult::Accept(Statement::Declaration {
|
||||
var: name,
|
||||
value: None,
|
||||
});
|
||||
}
|
||||
|
||||
// handle initialised case
|
||||
// expect equals
|
||||
let _ = expect_tt!(self.next()?, Assign)?;
|
||||
|
||||
// expect a valid expression
|
||||
let expr = self.parse_expression()?;
|
||||
|
||||
let _ = expect_tt!(self.next()?, Semicolon);
|
||||
|
||||
// return statement
|
||||
return ParseResult::Accept(Statement::Declaration {
|
||||
var: name,
|
||||
value: Some(expr),
|
||||
});
|
||||
}
|
||||
|
||||
// handle an in-place function call
|
||||
if let ParseResult::Accept(name) = expect_value!(self.peek_next()?, Identifier)
|
||||
&& let ParseResult::Accept(operator) = expect_tt!(
|
||||
self.peek(1)?,
|
||||
Assign,
|
||||
PlusEqual,
|
||||
MinusEqual,
|
||||
StarEqual,
|
||||
SlashEqual,
|
||||
PercentEqual,
|
||||
AndEqual,
|
||||
OrEqual,
|
||||
XorEqual,
|
||||
ShlEqual,
|
||||
ShrEqual
|
||||
)
|
||||
{
|
||||
// consume name token
|
||||
self.next()?;
|
||||
|
||||
// pattern match to find operator
|
||||
let operator = match operator {
|
||||
Token::Assign => AssignmentOperator::Assign,
|
||||
Token::PlusEqual => AssignmentOperator::AddAssign,
|
||||
Token::MinusEqual => AssignmentOperator::SubAssign,
|
||||
Token::StarEqual => AssignmentOperator::MulAssign,
|
||||
Token::SlashEqual => AssignmentOperator::DivAssign,
|
||||
Token::PercentEqual => AssignmentOperator::ModAssign,
|
||||
Token::AndEqual => AssignmentOperator::AndAssign,
|
||||
Token::OrEqual => AssignmentOperator::OrAssign,
|
||||
Token::XorEqual => AssignmentOperator::XorAssign,
|
||||
Token::ShlEqual => AssignmentOperator::LeftShiftAssign,
|
||||
Token::ShrEqual => AssignmentOperator::RightShiftAssign,
|
||||
_ => {
|
||||
return ParseResult::Reject(CompilerError::UnexpectedToken(
|
||||
self.peek_next()?.tt().to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// consume operator token
|
||||
self.next()?;
|
||||
|
||||
let value = self.parse_expression()?;
|
||||
|
||||
let _ = expect_tt!(self.next()?, Semicolon);
|
||||
|
||||
return ParseResult::Accept(Statement::Assign {
|
||||
varname: name.name,
|
||||
operator,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
// parse an expression and a semicolon
|
||||
let expr = self.parse_expression()?;
|
||||
let _ = expect_tt!(self.next()?, Semicolon)?;
|
||||
|
||||
ParseResult::Accept(Statement::Expression { expr })
|
||||
}
|
||||
|
||||
fn parse_expression(&mut self) -> ParseResult<Expression, CompilerError> {
|
||||
self.parse_logical_or()
|
||||
}
|
||||
|
||||
fn parse_logical_or(&mut self) -> ParseResult<Expression, CompilerError> {
|
||||
let left = self.parse_logical_and()?;
|
||||
|
||||
let op = match self.peek_next()? {
|
||||
Token::LogicalOr => BinaryOperator::LogicalOr,
|
||||
_ => return ParseResult::Accept(left),
|
||||
};
|
||||
|
||||
self.next()?;
|
||||
ParseResult::Accept(Expression::Binary {
|
||||
op,
|
||||
left: Box::new(left),
|
||||
right: Box::new(self.parse_logical_or()?),
|
||||
type_id: Some(TypeId::U32),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_logical_and(&mut self) -> ParseResult<Expression, CompilerError> {
|
||||
let left = self.parse_bitwise_or()?;
|
||||
|
||||
let op = match self.peek_next()? {
|
||||
Token::LogicalAnd => BinaryOperator::LogicalAnd,
|
||||
_ => return ParseResult::Accept(left),
|
||||
};
|
||||
|
||||
self.next()?;
|
||||
ParseResult::Accept(Expression::Binary {
|
||||
op,
|
||||
left: Box::new(left),
|
||||
right: Box::new(self.parse_logical_and()?),
|
||||
type_id: Some(TypeId::U32),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_bitwise_or(&mut self) -> ParseResult<Expression, CompilerError> {
|
||||
let left = self.parse_bitwise_xor()?;
|
||||
|
||||
let op = match self.peek_next()? {
|
||||
Token::Pipe => BinaryOperator::BitwiseOr,
|
||||
_ => return ParseResult::Accept(left),
|
||||
};
|
||||
|
||||
self.next()?;
|
||||
ParseResult::Accept(Expression::Binary {
|
||||
op,
|
||||
left: Box::new(left),
|
||||
right: Box::new(self.parse_bitwise_or()?),
|
||||
type_id: Some(TypeId::U32),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_bitwise_xor(&mut self) -> ParseResult<Expression, CompilerError> {
|
||||
let left = self.parse_bitwise_and()?;
|
||||
|
||||
let op = match self.peek_next()? {
|
||||
Token::Caret => BinaryOperator::BitwiseXor,
|
||||
_ => return ParseResult::Accept(left),
|
||||
};
|
||||
|
||||
self.next()?;
|
||||
ParseResult::Accept(Expression::Binary {
|
||||
op,
|
||||
left: Box::new(left),
|
||||
right: Box::new(self.parse_bitwise_xor()?),
|
||||
type_id: Some(TypeId::U32),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_bitwise_and(&mut self) -> ParseResult<Expression, CompilerError> {
|
||||
let left = self.parse_comparison()?;
|
||||
|
||||
let op = match self.peek_next()? {
|
||||
Token::Ampersand => BinaryOperator::BitwiseAnd,
|
||||
_ => return ParseResult::Accept(left),
|
||||
};
|
||||
|
||||
self.next()?;
|
||||
ParseResult::Accept(Expression::Binary {
|
||||
op,
|
||||
left: Box::new(left),
|
||||
right: Box::new(self.parse_bitwise_and()?),
|
||||
type_id: Some(TypeId::U32),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_comparison(&mut self) -> ParseResult<Expression, CompilerError> {
|
||||
let left = self.parse_shift()?;
|
||||
|
||||
let op = match self.peek_next()? {
|
||||
Token::EqualEqual => BinaryOperator::Equal,
|
||||
Token::BangEqual => BinaryOperator::NotEqual,
|
||||
Token::Less => BinaryOperator::LessThan,
|
||||
Token::Greater => BinaryOperator::GreaterThan,
|
||||
Token::LessEqual => BinaryOperator::LessOrEqual,
|
||||
Token::GreaterEqual => BinaryOperator::GreaterOrEqual,
|
||||
_ => return ParseResult::Accept(left),
|
||||
};
|
||||
|
||||
self.next()?;
|
||||
ParseResult::Accept(Expression::Binary {
|
||||
op,
|
||||
left: Box::new(left),
|
||||
right: Box::new(self.parse_comparison()?),
|
||||
type_id: Some(TypeId::Bool),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_shift(&mut self) -> ParseResult<Expression, CompilerError> {
|
||||
let left = self.parse_additive()?;
|
||||
|
||||
let op = match self.peek_next()? {
|
||||
Token::LeftShift => BinaryOperator::LeftShift,
|
||||
Token::RightShift => BinaryOperator::RightShift,
|
||||
_ => return ParseResult::Accept(left),
|
||||
};
|
||||
|
||||
self.next()?;
|
||||
ParseResult::Accept(Expression::Binary {
|
||||
op,
|
||||
left: Box::new(left),
|
||||
right: Box::new(self.parse_shift()?),
|
||||
type_id: Some(TypeId::U32),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_additive(&mut self) -> ParseResult<Expression, CompilerError> {
|
||||
let left = self.parse_multiplicative()?;
|
||||
|
||||
let op = match self.peek_next()? {
|
||||
Token::Plus => BinaryOperator::Add,
|
||||
Token::Minus => BinaryOperator::Sub,
|
||||
_ => return ParseResult::Accept(left),
|
||||
};
|
||||
|
||||
self.next()?;
|
||||
ParseResult::Accept(Expression::Binary {
|
||||
op,
|
||||
left: Box::new(left),
|
||||
right: Box::new(self.parse_additive()?),
|
||||
type_id: Some(TypeId::U32),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_multiplicative(&mut self) -> ParseResult<Expression, CompilerError> {
|
||||
let left = self.parse_unary()?;
|
||||
|
||||
let op = match self.peek_next()? {
|
||||
Token::Star => BinaryOperator::Mul,
|
||||
Token::Slash => BinaryOperator::Div,
|
||||
_ => return ParseResult::Accept(left),
|
||||
};
|
||||
|
||||
self.next()?;
|
||||
ParseResult::Accept(Expression::Binary {
|
||||
op,
|
||||
left: Box::new(left),
|
||||
right: Box::new(self.parse_multiplicative()?),
|
||||
type_id: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_unary(&mut self) -> ParseResult<Expression, CompilerError> {
|
||||
let op = match self.peek_next()? {
|
||||
// prefix inc/dec
|
||||
Token::PlusPlus => UnaryOperator::Increment,
|
||||
Token::MinusMinus => UnaryOperator::Decrement,
|
||||
|
||||
// arithmetic
|
||||
Token::Plus => UnaryOperator::Plus,
|
||||
Token::Minus => UnaryOperator::Minus,
|
||||
|
||||
// pointer
|
||||
Token::Star => UnaryOperator::Dereference,
|
||||
Token::Ampersand => UnaryOperator::AddressOf,
|
||||
|
||||
// boolean
|
||||
Token::Bang => UnaryOperator::LogicalNot,
|
||||
Token::Tilde => UnaryOperator::BitwiseNot,
|
||||
|
||||
Token::SizeOf => UnaryOperator::SizeOf,
|
||||
_ => {
|
||||
let expr = self.parse_primary()?;
|
||||
return self.parse_postfix(expr);
|
||||
}
|
||||
};
|
||||
|
||||
self.next()?;
|
||||
let operand = Box::new(self.parse_unary()?);
|
||||
ParseResult::Accept(Expression::Unary {
|
||||
op,
|
||||
operand,
|
||||
type_id: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_postfix(
|
||||
&mut self,
|
||||
mut expr: Expression,
|
||||
) -> ParseResult<Expression, CompilerError> {
|
||||
loop {
|
||||
match self.peek_next()? {
|
||||
// Type cast: expr as Type
|
||||
Token::As => {
|
||||
self.next()?; // consume 'as'
|
||||
let target_type = self.parse_type()?;
|
||||
expr = Expression::TypeCast {
|
||||
expr: Box::new(expr),
|
||||
target_type,
|
||||
type_id: None,
|
||||
};
|
||||
}
|
||||
|
||||
// Postfix increment/decrement
|
||||
Token::PlusPlus => {
|
||||
self.next()?;
|
||||
expr = Expression::UnaryPostfix {
|
||||
op: UnaryOperator::Increment,
|
||||
operand: Box::new(expr),
|
||||
type_id: None,
|
||||
};
|
||||
}
|
||||
Token::MinusMinus => {
|
||||
self.next()?;
|
||||
expr = Expression::UnaryPostfix {
|
||||
op: UnaryOperator::Decrement,
|
||||
operand: Box::new(expr),
|
||||
type_id: None,
|
||||
};
|
||||
}
|
||||
|
||||
// Array indexing: expr[index]
|
||||
Token::LeftBracket => {
|
||||
self.next()?; // consume '['
|
||||
let index = Box::new(self.parse_expression()?);
|
||||
|
||||
let _ = expect_tt!(self.next()?, RightBracket)?;
|
||||
|
||||
expr = Expression::IndexAccess {
|
||||
expr: Box::new(expr),
|
||||
index,
|
||||
type_id: None,
|
||||
};
|
||||
}
|
||||
|
||||
// Function call: expr(args...)
|
||||
Token::LeftParen => {
|
||||
self.next()?; // consume '('
|
||||
let mut args = Vec::new();
|
||||
|
||||
if !matches!(self.peek_next()?, Token::RightParen) {
|
||||
loop {
|
||||
args.push(self.parse_expression()?);
|
||||
if !matches!(self.peek_next()?, Token::Comma) {
|
||||
break;
|
||||
}
|
||||
self.next()?; // consume comma
|
||||
}
|
||||
}
|
||||
|
||||
let _ = expect_tt!(self.next()?, RightParen)?;
|
||||
|
||||
if let Expression::Variable { name, .. } = expr {
|
||||
expr = Expression::Call {
|
||||
func: Call { name, args },
|
||||
type_id: None,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Member access: expr.member (if you support structs)
|
||||
Token::Dot => {
|
||||
self.next()?;
|
||||
let field_name = expect_value!(self.next()?, Identifier)?;
|
||||
expr = Expression::MemberAccess {
|
||||
expr: Box::new(expr),
|
||||
field_name,
|
||||
type_id: None,
|
||||
};
|
||||
}
|
||||
|
||||
// No more postfix operations
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
ParseResult::Accept(expr)
|
||||
}
|
||||
|
||||
fn parse_primary(&mut self) -> ParseResult<Expression, CompilerError> {
|
||||
match self.peek_next()? {
|
||||
Token::UnsignedInt(value, type_id) => {
|
||||
self.next()?;
|
||||
ParseResult::Accept(Expression::Number(Number::Unsigned(value, type_id)))
|
||||
}
|
||||
Token::SignedInt(value, type_id) => {
|
||||
self.next()?;
|
||||
ParseResult::Accept(Expression::Number(Number::Signed(value, type_id)))
|
||||
}
|
||||
Token::String(value) => {
|
||||
self.next()?;
|
||||
ParseResult::Accept(Expression::StringLiteral(value))
|
||||
}
|
||||
Token::Char(value) => {
|
||||
self.next()?;
|
||||
ParseResult::Accept(Expression::CharLiteral(value))
|
||||
}
|
||||
|
||||
Token::Identifier(name) => {
|
||||
self.next()?;
|
||||
|
||||
// if the next token isn't the beginning of a struct literal this is just
|
||||
// an identifier.
|
||||
if !expect_tt!(self.peek_next()?, LeftBrace).accepted() {
|
||||
return ParseResult::Accept(Expression::Variable {
|
||||
name,
|
||||
expr_type: None,
|
||||
});
|
||||
}
|
||||
|
||||
let _ = self.next()?;
|
||||
|
||||
let mut fields = Vec::new();
|
||||
while !expect_tt!(self.peek_next()?, RightBrace).accepted() {
|
||||
let name = expect_value!(self.next()?, Identifier)?;
|
||||
let _ = expect_tt!(self.next()?, Colon)?;
|
||||
let expr = self.parse_expression()?;
|
||||
|
||||
fields.push((name, expr));
|
||||
|
||||
if expect_tt!(self.peek_next()?, Comma).accepted() {
|
||||
self.next()?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let _ = expect_tt!(self.next()?, RightBrace)?;
|
||||
|
||||
ParseResult::Accept(Expression::StructLiteral {
|
||||
name,
|
||||
fields,
|
||||
type_id: None,
|
||||
})
|
||||
}
|
||||
Token::LeftBracket => {
|
||||
self.next()?; // consume '['
|
||||
let mut elements = Vec::new();
|
||||
|
||||
if !matches!(self.peek_next()?, Token::RightBracket) {
|
||||
loop {
|
||||
elements.push(self.parse_expression()?);
|
||||
if !matches!(self.peek_next()?, Token::Comma) {
|
||||
break;
|
||||
}
|
||||
self.next()?; // consume comma
|
||||
}
|
||||
}
|
||||
|
||||
expect_tt!(self.next()?, RightBracket)?;
|
||||
ParseResult::Accept(Expression::ArrayLiteral {
|
||||
elements,
|
||||
type_id: None,
|
||||
})
|
||||
}
|
||||
Token::LeftParen => {
|
||||
self.next()?;
|
||||
let expr = self.parse_expression()?;
|
||||
let _ = expect_tt!(self.next()?, RightParen)?;
|
||||
ParseResult::Accept(expr)
|
||||
}
|
||||
_ => ParseResult::Reject(CompilerError::UnexpectedToken(
|
||||
self.peek_next()?.tt().to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_var_decl(&mut self) -> ParseResult<Variable, CompilerError> {
|
||||
let name = expect_value!(self.next()?, Identifier)?;
|
||||
|
||||
let _ = expect_tt!(self.next()?, Colon)?;
|
||||
|
||||
let type_id = self.parse_type()?;
|
||||
|
||||
ParseResult::Accept(Variable {
|
||||
name: name.name,
|
||||
type_id,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_type(&mut self) -> ParseResult<TypeId, CompilerError> {
|
||||
// parse primitive or named type
|
||||
if expect_tt!(self.peek_next()?, Identifier).accepted() {
|
||||
return self.parse_type_identifier();
|
||||
}
|
||||
|
||||
// parse array type
|
||||
if expect_tt!(self.peek_next()?, LeftBracket).accepted() {
|
||||
let _ = self.next()?;
|
||||
|
||||
let internal_type = self.parse_type()?;
|
||||
let _ = expect_tt!(self.next()?, Semicolon)?;
|
||||
|
||||
let size = expect_value!(self.next()?, UnsignedInt)?;
|
||||
|
||||
let _ = expect_tt!(self.next()?, RightBracket)?;
|
||||
|
||||
return ParseResult::Accept(TypeId::Array {
|
||||
r#type: Box::new(internal_type),
|
||||
size: size as usize,
|
||||
});
|
||||
}
|
||||
|
||||
// parse tuple type
|
||||
if expect_tt!(self.peek_next()?, LeftParen).accepted() {
|
||||
let _ = self.next()?;
|
||||
|
||||
let mut types = Vec::new();
|
||||
while !expect_tt!(self.peek_next()?, RightParen).accepted() {
|
||||
types.push(self.parse_type()?);
|
||||
if !expect_tt!(self.peek_next()?, Comma).accepted() {
|
||||
break;
|
||||
}
|
||||
let _ = self.next()?;
|
||||
}
|
||||
let _ = expect_tt!(self.next()?, RightParen)?;
|
||||
|
||||
return ParseResult::Accept(TypeId::Tuple(types));
|
||||
}
|
||||
|
||||
ParseResult::Reject(CompilerError::Generic(format!(
|
||||
"Parsing type but no valid type was detected: {:?}",
|
||||
self.peek_next()?
|
||||
)))
|
||||
}
|
||||
|
||||
fn parse_type_identifier(&mut self) -> ParseResult<TypeId, CompilerError> {
|
||||
// get the type name incl namespace
|
||||
let name = expect_value!(self.next()?, Identifier)?;
|
||||
|
||||
let type_id = match name.name.as_str() {
|
||||
"u32" => TypeId::U32,
|
||||
"u16" => TypeId::U16,
|
||||
"u8" => TypeId::U8,
|
||||
"i32" => TypeId::I32,
|
||||
"i16" => TypeId::I16,
|
||||
"i8" => TypeId::I8,
|
||||
"void" => TypeId::Void,
|
||||
"char" => TypeId::Char,
|
||||
"str" => TypeId::Ptr(Box::new(TypeId::Char)),
|
||||
_ => {
|
||||
let mut generics = Vec::new();
|
||||
if expect_tt!(self.peek_next()?, Less).accepted() {
|
||||
let _ = self.next()?;
|
||||
|
||||
// loop until we find the closing '>'
|
||||
while !expect_tt!(self.peek_next()?, Greater).accepted() {
|
||||
generics.push(self.parse_type()?);
|
||||
if !expect_tt!(self.peek_next()?, Comma).accepted() {
|
||||
break;
|
||||
}
|
||||
let _ = self.next()?;
|
||||
}
|
||||
let _ = expect_tt!(self.next()?, Greater)?;
|
||||
}
|
||||
|
||||
TypeId::UnknownCustom { name, generics }
|
||||
}
|
||||
};
|
||||
|
||||
ParseResult::Accept(type_id)
|
||||
}
|
||||
|
||||
fn next(&mut self) -> ParseResult<Token, CompilerError> {
|
||||
if self.idx >= self.tokens.len() {
|
||||
ParseResult::Reject(CompilerError::UnexpectedEndOfInput)
|
||||
} else {
|
||||
let token = self.tokens[self.idx].clone();
|
||||
self.idx += 1;
|
||||
ParseResult::Accept(token)
|
||||
}
|
||||
}
|
||||
|
||||
fn peek_next(&self) -> ParseResult<Token, CompilerError> {
|
||||
if self.idx >= self.tokens.len() {
|
||||
ParseResult::Reject(CompilerError::UnexpectedEndOfInput)
|
||||
} else {
|
||||
ParseResult::Accept(self.tokens[self.idx].clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn peek(&self, offset: usize) -> ParseResult<Token, CompilerError> {
|
||||
if self.idx + offset >= self.tokens.len() {
|
||||
ParseResult::Reject(CompilerError::UnexpectedEndOfInput)
|
||||
} else {
|
||||
ParseResult::Accept(self.tokens[self.idx + offset].clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> ParseResult<T, E> {
|
||||
pub fn accepted(&self) -> bool {
|
||||
matches!(self, ParseResult::Accept(_))
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ParseResultResidual<T> {
|
||||
Deny,
|
||||
Reject(T),
|
||||
}
|
||||
|
||||
impl<T, E> Try for ParseResult<T, E> {
|
||||
type Output = T;
|
||||
type Residual = ParseResultResidual<E>;
|
||||
|
||||
fn from_output(output: T) -> Self {
|
||||
ParseResult::Accept(output)
|
||||
}
|
||||
|
||||
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
|
||||
match self {
|
||||
ParseResult::Accept(v) => ControlFlow::Continue(v),
|
||||
ParseResult::Deny => ControlFlow::Break(ParseResultResidual::Deny),
|
||||
ParseResult::Reject(e) => ControlFlow::Break(ParseResultResidual::Reject(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> FromResidual for ParseResult<T, E> {
|
||||
fn from_residual(residual: ParseResultResidual<E>) -> Self {
|
||||
match residual {
|
||||
ParseResultResidual::Deny => ParseResult::Deny,
|
||||
ParseResultResidual::Reject(e) => ParseResult::Reject(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! expect_tt {
|
||||
($token:expr, $($variant:ident),+) => {{
|
||||
let token = $token.clone();
|
||||
let tt = token.tt().to_string();
|
||||
|
||||
let mut vs = String::new();
|
||||
$(
|
||||
let s = stringify!($variant);
|
||||
vs.push_str(s);
|
||||
vs.push_str("|");
|
||||
)+
|
||||
|
||||
match tt.as_str() {
|
||||
$(
|
||||
stringify!($variant) => ParseResult::Accept(token),
|
||||
)+
|
||||
_ => {
|
||||
// let expected = format!("[{}]", vec![$(stringify!($variant)),+].join(" | "));
|
||||
ParseResult::Reject(CompilerError::UnexpectedToken(tt))
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! expect_value {
|
||||
($expr:expr, $variant:ident) => {{
|
||||
let tok = $expr;
|
||||
match tok.clone() {
|
||||
Token::$variant(first, ..) => ParseResult::Accept(first),
|
||||
_ => {
|
||||
ParseResult::Reject(CompilerError::UnexpectedToken(tok.tt().to_string()))
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::model::{
|
||||
BinaryOperator, // You'll need to add this to your imports
|
||||
CompilerError,
|
||||
Declaration,
|
||||
Dependency,
|
||||
Expression,
|
||||
Program,
|
||||
TypeId,
|
||||
UnaryOperator,
|
||||
};
|
||||
|
||||
pub struct Analyser {
|
||||
symbol_table: HashMap<String, Declaration>,
|
||||
}
|
||||
|
||||
const NUMERIC_TYPES: &[TypeId] = &[
|
||||
TypeId::U32,
|
||||
TypeId::I32,
|
||||
TypeId::I16,
|
||||
TypeId::U16,
|
||||
TypeId::I8,
|
||||
TypeId::U8,
|
||||
];
|
||||
|
||||
impl Analyser {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
symbol_table: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn analyse(&mut self, ast: Program) -> Result<(), CompilerError> {
|
||||
// build table of global symbols.
|
||||
for dec in ast.declarations {
|
||||
let name = match dec.clone() {
|
||||
Declaration::Function { name, .. } => name,
|
||||
Declaration::Variable { var, .. } => var.name,
|
||||
Declaration::Dependency(Dependency { name, .. }) => name,
|
||||
};
|
||||
|
||||
self.symbol_table.insert(name, dec);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn match_type(
|
||||
actual: TypeId,
|
||||
expected: Option<TypeId>,
|
||||
) -> Result<TypeId, CompilerError> {
|
||||
match expected {
|
||||
Some(id) => {
|
||||
if id != actual {
|
||||
Err(CompilerError::TypeMismatch(id, actual))
|
||||
} else {
|
||||
Ok(actual)
|
||||
}
|
||||
}
|
||||
None => Ok(actual),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_type(
|
||||
&mut self, // Changed from &self to &mut self since we modify expr
|
||||
expr: &mut Expression,
|
||||
expected_type: Option<TypeId>,
|
||||
) -> Result<TypeId, CompilerError> {
|
||||
match expr {
|
||||
// Correct IFF we're expecting a void type
|
||||
Expression::Empty => Self::match_type(TypeId::Void, expected_type),
|
||||
|
||||
// Correct IFF we're expecting a char type
|
||||
Expression::CharLiteral(_) => Self::match_type(TypeId::Char, expected_type),
|
||||
|
||||
// Correct IFF we're expecting a string slice type
|
||||
Expression::StringLiteral(_) => {
|
||||
Self::match_type(TypeId::Ptr(Box::new(TypeId::Char)), expected_type)
|
||||
}
|
||||
|
||||
Expression::Variable { name, expr_type } => {
|
||||
let actual = expr_type.clone().ok_or(CompilerError::UnknownType)?;
|
||||
Self::match_type(actual, expected_type)
|
||||
}
|
||||
|
||||
Expression::Number { value, type_id } => {
|
||||
// If we already know the TypeId
|
||||
if let Some(id) = type_id {
|
||||
return Self::match_type(id.clone(), expected_type);
|
||||
}
|
||||
|
||||
// If we're expecting a type id, check it's numeric.
|
||||
// TODO: add checks to make sure it's valid for its size eg u8 cant be
|
||||
// more than 255
|
||||
if let Some(expected) = expected_type {
|
||||
if NUMERIC_TYPES.contains(&expected) {
|
||||
*type_id = Some(expected.clone());
|
||||
return Ok(expected);
|
||||
} else {
|
||||
return Err(CompilerError::TypeMismatch(expected, TypeId::U32));
|
||||
}
|
||||
}
|
||||
|
||||
// Default to i32 if no type information is available
|
||||
*type_id = Some(TypeId::I32);
|
||||
Ok(TypeId::I32)
|
||||
}
|
||||
|
||||
Expression::Binary {
|
||||
op,
|
||||
left,
|
||||
right,
|
||||
type_id,
|
||||
} => {
|
||||
// For binary operations, both operands should have compatible types
|
||||
// and the result type depends on the operation
|
||||
let left_type = self.get_type(left, None)?;
|
||||
let right_type = self.get_type(right, Some(left_type.clone()))?;
|
||||
|
||||
// For numeric operations, result has the same type as operands
|
||||
if NUMERIC_TYPES.contains(&left_type)
|
||||
&& NUMERIC_TYPES.contains(&right_type)
|
||||
{
|
||||
*type_id = Some(left_type);
|
||||
Self::match_type(left_type, expected_type)
|
||||
} else {
|
||||
Err(CompilerError::TypeMismatch(left_type, right_type))
|
||||
}
|
||||
}
|
||||
|
||||
Expression::Unary {
|
||||
op,
|
||||
operand,
|
||||
type_id,
|
||||
} => {
|
||||
match op {
|
||||
UnaryOperator::Plus | UnaryOperator::Minus => {
|
||||
// Unary +/- require numeric operands
|
||||
let inner_type = self.get_type(operand, None)?;
|
||||
|
||||
if NUMERIC_TYPES.contains(&inner_type) {
|
||||
*type_id = Some(inner_type.clone());
|
||||
Self::match_type(inner_type, expected_type)
|
||||
} else {
|
||||
Err(CompilerError::TypeMismatch(inner_type, TypeId::I32))
|
||||
}
|
||||
}
|
||||
|
||||
UnaryOperator::Dereference => {
|
||||
// For dereference (*ptr), the operand must be a pointer
|
||||
// and the result type is what the pointer points to
|
||||
let inner_type = self.get_type(operand, None)?;
|
||||
|
||||
match inner_type {
|
||||
TypeId::Ptr(inner) => {
|
||||
let deref_type = *inner;
|
||||
*type_id = Some(deref_type.clone());
|
||||
Self::match_type(deref_type, expected_type)
|
||||
}
|
||||
_ => Err(CompilerError::Generic(format!(
|
||||
"Cannot dereference non-pointer type: {:?}",
|
||||
inner_type
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
UnaryOperator::Reference => {
|
||||
// For reference (&var), we need to determine what we're taking
|
||||
// a reference to, then wrap it in a Ptr
|
||||
// If expected_type is Ptr(T), then operand should have type T
|
||||
let expected_inner = match expected_type.clone() {
|
||||
Some(TypeId::Ptr(inner)) => Some(*inner),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let inner_type = self.get_type(operand, expected_inner)?;
|
||||
let ref_type = TypeId::Ptr(Box::new(inner_type));
|
||||
*type_id = Some(ref_type.clone());
|
||||
Self::match_type(ref_type, expected_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Expression::Call {
|
||||
name,
|
||||
args,
|
||||
type_id,
|
||||
} => match self.symbol_table.get(&name.name) {
|
||||
Some(Declaration::Function {
|
||||
params,
|
||||
return_type,
|
||||
..
|
||||
}) => {
|
||||
// check that we've given the right number of arguments.
|
||||
if args.len() != params.len() {
|
||||
return Err(CompilerError::Generic(format!(
|
||||
"Function {} expected {} arguments but received {}",
|
||||
name.name,
|
||||
params.len(),
|
||||
args.len()
|
||||
)));
|
||||
}
|
||||
|
||||
for (arg, param) in args.iter_mut().zip(params.iter()) {
|
||||
// check that the argument type matches the parameter type.
|
||||
let provided_type = self.get_type(arg, Some(param.type_id))?;
|
||||
if provided_type != param.type_id {
|
||||
return Err(CompilerError::TypeMismatch(
|
||||
param.type_id,
|
||||
provided_type,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
*type_id = Some(return_type.clone());
|
||||
Self::match_type(return_type.clone(), expected_type)
|
||||
}
|
||||
_ => Err(CompilerError::Generic(format!(
|
||||
"Function {} not found in symbol table",
|
||||
name.name
|
||||
))),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
use common::logging::Logger;
|
||||
|
||||
use crate::model::{CompilerError, Program};
|
||||
|
||||
// mod c;
|
||||
mod dsc;
|
||||
|
||||
pub fn compiler_frontend(
|
||||
ext: &str,
|
||||
data: &str,
|
||||
logger: &Logger,
|
||||
) -> Result<Program, CompilerError> {
|
||||
match ext {
|
||||
"dsc" => Ok(dsc::generate_ast(&data, &logger)?),
|
||||
// "c" => Ok(c::generate_ast(&data)?),
|
||||
_ => Err(CompilerError::Generic(format!(
|
||||
"File type {} not supported",
|
||||
ext
|
||||
))),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
#![feature(try_trait_v2)]
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use common::{
|
||||
build::{BuildError, Builder},
|
||||
logging::LogReceiver,
|
||||
};
|
||||
|
||||
use crate::{model::CompilerError, specialised::build_specialised};
|
||||
|
||||
mod backend;
|
||||
mod frontend;
|
||||
mod model;
|
||||
mod specialised;
|
||||
|
||||
pub struct Compiler {
|
||||
src_path: PathBuf,
|
||||
result: Option<Result<String, BuildError>>,
|
||||
logger: LogReceiver,
|
||||
}
|
||||
|
||||
impl Compiler {
|
||||
fn build(&mut self) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let input =
|
||||
std::fs::read_to_string(&self.src_path).expect("Failed to read input file");
|
||||
|
||||
let input_ext = self
|
||||
.src_path
|
||||
.extension()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("");
|
||||
|
||||
// check if we're using a specialised compiler
|
||||
if let Some(output) = build_specialised(input_ext, &input) {
|
||||
return output.map_err(|err| format!("Compilation failed: {err:?}").into());
|
||||
}
|
||||
|
||||
// Parse the input using the frontend, providing the file extension and data.
|
||||
let ast =
|
||||
match frontend::compiler_frontend(input_ext, &input, &self.logger.logger()) {
|
||||
Ok(ast) => ast,
|
||||
Err(err) => return Err(format!("Compilation failed: {err:?}").into()),
|
||||
};
|
||||
|
||||
// println!("Parsed AST: {:#?}", ast);
|
||||
|
||||
// Generate the output using the backend with the parsed result.
|
||||
let result = match backend::compiler_backend("dsa", &ast) {
|
||||
Ok(result) => result,
|
||||
Err(err) => return Err(format!("Compilation failed: {err:?}").into()),
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Builder for Compiler {
|
||||
type Output = String;
|
||||
|
||||
fn new(src_path: impl Into<PathBuf>) -> Self {
|
||||
Self {
|
||||
src_path: src_path.into(),
|
||||
result: None,
|
||||
logger: LogReceiver::new(true),
|
||||
}
|
||||
}
|
||||
|
||||
fn start(&mut self) {
|
||||
match self.build() {
|
||||
Ok(x) => self.result = Some(Ok(x)),
|
||||
Err(err) => self.result = Some(Err(err.into())),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll(&mut self) -> Option<Result<Self::Output, BuildError>> {
|
||||
self.result.take()
|
||||
}
|
||||
|
||||
fn output(&mut self) -> Result<Self::Output, BuildError> {
|
||||
self.result.clone().ok_or(BuildError::Generic(String::from(
|
||||
"Compiler was never started",
|
||||
)))?
|
||||
}
|
||||
|
||||
fn logs(&self) -> Vec<String> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error(msg: impl Into<String>) -> CompilerError {
|
||||
CompilerError::Generic(msg.into())
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use common::{build::Builder, logging::info};
|
||||
use compiler::Compiler;
|
||||
|
||||
fn main() {
|
||||
// read from input file: syntax "c_compiler <src.c> [output.dsa]"
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() < 2 {
|
||||
eprintln!("Usage: c_compiler <src.dsc> [output.dsa]");
|
||||
return;
|
||||
}
|
||||
|
||||
let input_file = &args[1];
|
||||
let output_file = if args.len() > 2 {
|
||||
&args[2]
|
||||
} else {
|
||||
"output.dsa"
|
||||
};
|
||||
|
||||
{
|
||||
let mut builder = Compiler::new(PathBuf::from(input_file));
|
||||
builder.start();
|
||||
let result = builder.output().unwrap();
|
||||
|
||||
std::fs::write(output_file, &result).expect("Failed to write output");
|
||||
|
||||
info(&format!(
|
||||
"Compilation Successful ✅ \n\tSource: {}\n\tOutput: {}\n",
|
||||
input_file, output_file,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
use core::fmt;
|
||||
|
||||
use common::build::BuildError;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CompilerError {
|
||||
UnexpectedToken(String),
|
||||
UnexpectedEndOfInput,
|
||||
UnexpectedCharacter(char),
|
||||
Undefined(Name),
|
||||
InvalidSyntax(String),
|
||||
Generic(String),
|
||||
UnknownType,
|
||||
TypeMismatch(TypeId, TypeId),
|
||||
Unimplemented(String),
|
||||
}
|
||||
|
||||
impl From<CompilerError> for BuildError {
|
||||
fn from(err: CompilerError) -> Self {
|
||||
BuildError::Generic(format!("{:?}", err))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Name {
|
||||
pub name: String,
|
||||
pub namespace: Option<String>,
|
||||
}
|
||||
impl Name {
|
||||
pub fn new(name: impl Into<String>, namespace: Option<String>) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
namespace,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Program {
|
||||
pub declarations: Vec<Declaration>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Declaration {
|
||||
Function {
|
||||
name: String,
|
||||
return_type: TypeId,
|
||||
params: Vec<Variable>,
|
||||
body: Block,
|
||||
},
|
||||
Variable {
|
||||
var: Variable,
|
||||
init: Option<ConstExpr>,
|
||||
is_const: bool,
|
||||
},
|
||||
Dependency(Dependency),
|
||||
Struct {
|
||||
name: Name,
|
||||
fields: Vec<Variable>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Dependency {
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TypeId {
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
Bool,
|
||||
Char,
|
||||
Void,
|
||||
Ptr(Box<TypeId>),
|
||||
Ref(Box<TypeId>),
|
||||
Tuple(Vec<TypeId>),
|
||||
Array {
|
||||
r#type: Box<TypeId>,
|
||||
size: usize,
|
||||
},
|
||||
UnknownCustom {
|
||||
name: Name,
|
||||
generics: Vec<TypeId>,
|
||||
},
|
||||
Struct {
|
||||
name: Name,
|
||||
fields: Vec<TypeId>,
|
||||
generics: Vec<TypeId>,
|
||||
},
|
||||
}
|
||||
|
||||
impl TypeId {
|
||||
pub fn size(&self) -> usize {
|
||||
match self {
|
||||
Self::U8 => 1,
|
||||
Self::U16 => 2,
|
||||
Self::U32 => 4,
|
||||
Self::I8 => 1,
|
||||
Self::I16 => 2,
|
||||
Self::I32 => 4,
|
||||
Self::Bool => 1,
|
||||
Self::Char => 1,
|
||||
Self::Void => 0,
|
||||
Self::Ptr(t) => t.size(),
|
||||
Self::Ref(t) => t.size(),
|
||||
Self::Tuple(types) => types.iter().map(|t| t.size()).sum(),
|
||||
Self::Array { r#type, size } => r#type.size() * size,
|
||||
Self::UnknownCustom { .. } => 1, /* TODO: calculate type size during */
|
||||
// semantic analysis
|
||||
Self::Struct { fields, .. } => fields.iter().map(|t| t.size()).sum(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TypeId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::U8 => write!(f, "u8"),
|
||||
Self::U16 => write!(f, "u16"),
|
||||
Self::U32 => write!(f, "u32"),
|
||||
Self::I8 => write!(f, "i8"),
|
||||
Self::I16 => write!(f, "i16"),
|
||||
Self::I32 => write!(f, "i32"),
|
||||
Self::Bool => write!(f, "bool"),
|
||||
Self::Char => write!(f, "char"),
|
||||
Self::Void => write!(f, "void"),
|
||||
Self::Ptr(t) => write!(f, "*{}", t),
|
||||
Self::Ref(t) => write!(f, "&{}", t),
|
||||
Self::Tuple(elems) => write!(
|
||||
f,
|
||||
"({})",
|
||||
elems
|
||||
.iter()
|
||||
.map(|t| t.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
),
|
||||
Self::Array { r#type, size } => write!(f, "[{}; {}]", r#type, size),
|
||||
Self::UnknownCustom { name, generics } => {
|
||||
write!(
|
||||
f,
|
||||
"{}<{}>",
|
||||
name,
|
||||
generics
|
||||
.iter()
|
||||
.map(|t| t.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
Self::Struct {
|
||||
name,
|
||||
fields,
|
||||
generics,
|
||||
} => write!(
|
||||
f,
|
||||
"struct<{}> {} {{{}}}",
|
||||
generics
|
||||
.iter()
|
||||
.map(|t| t.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", "),
|
||||
name,
|
||||
fields
|
||||
.iter()
|
||||
.map(|t| t.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Block = Vec<Statement>;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Variable {
|
||||
pub name: String,
|
||||
pub type_id: TypeId,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Statement {
|
||||
Block(Block),
|
||||
Declaration {
|
||||
var: Variable,
|
||||
value: Option<Expression>,
|
||||
},
|
||||
Assign {
|
||||
varname: String,
|
||||
operator: AssignmentOperator,
|
||||
value: Expression,
|
||||
},
|
||||
PtrWrite {
|
||||
ptr: Expression,
|
||||
value: Expression,
|
||||
},
|
||||
Expression {
|
||||
expr: Expression,
|
||||
},
|
||||
If {
|
||||
condition: Expression,
|
||||
then_stmt: Block,
|
||||
else_stmt: Block,
|
||||
},
|
||||
While {
|
||||
condition: Expression,
|
||||
body: Vec<Statement>,
|
||||
},
|
||||
Loop(Block),
|
||||
Defer(Call),
|
||||
Break,
|
||||
Continue,
|
||||
Return(Option<Expression>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ConstExpr {
|
||||
Number(i32),
|
||||
String(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for ConstExpr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ConstExpr::Number(n) => write!(f, "{}", n),
|
||||
ConstExpr::String(s) => write!(f, "\"{}\"", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Expression {
|
||||
Empty,
|
||||
Binary {
|
||||
op: BinaryOperator,
|
||||
left: Box<Expression>,
|
||||
right: Box<Expression>,
|
||||
|
||||
// Post-Semantic Analysis
|
||||
type_id: Option<TypeId>,
|
||||
},
|
||||
Unary {
|
||||
op: UnaryOperator,
|
||||
operand: Box<Expression>,
|
||||
|
||||
// Post-Semantic Analysis
|
||||
type_id: Option<TypeId>,
|
||||
},
|
||||
UnaryPostfix {
|
||||
op: UnaryOperator,
|
||||
operand: Box<Expression>,
|
||||
|
||||
// Post-Semantic Analysis
|
||||
type_id: Option<TypeId>,
|
||||
},
|
||||
Variable {
|
||||
name: Name,
|
||||
expr_type: Option<TypeId>,
|
||||
},
|
||||
TypeCast {
|
||||
expr: Box<Expression>,
|
||||
target_type: TypeId,
|
||||
|
||||
// Post-Semantic Analysis
|
||||
type_id: Option<TypeId>,
|
||||
},
|
||||
IndexAccess {
|
||||
expr: Box<Expression>,
|
||||
index: Box<Expression>,
|
||||
|
||||
// Post-Semantic Analysis
|
||||
type_id: Option<TypeId>,
|
||||
},
|
||||
MemberAccess {
|
||||
expr: Box<Expression>,
|
||||
field_name: Name,
|
||||
|
||||
// Post-Semantic Analysis
|
||||
type_id: Option<TypeId>,
|
||||
},
|
||||
Call {
|
||||
func: Call,
|
||||
|
||||
// Post-Semantic Analysis
|
||||
type_id: Option<TypeId>,
|
||||
},
|
||||
Number(Number),
|
||||
StringLiteral(String),
|
||||
CharLiteral(char),
|
||||
ArrayLiteral {
|
||||
elements: Vec<Expression>,
|
||||
type_id: Option<TypeId>,
|
||||
},
|
||||
StructLiteral {
|
||||
name: Name,
|
||||
fields: Vec<(Name, Expression)>,
|
||||
type_id: Option<TypeId>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Number {
|
||||
Signed(i32, Option<TypeId>),
|
||||
Unsigned(u32, Option<TypeId>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Call {
|
||||
pub name: Name,
|
||||
pub args: Vec<Expression>,
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
pub fn is_pure(&self) -> bool {
|
||||
match self {
|
||||
Expression::Number { .. } => true,
|
||||
Expression::StringLiteral(_) => true,
|
||||
Expression::CharLiteral(_) => true,
|
||||
Expression::Call { .. } => false,
|
||||
Expression::Binary { left, right, .. } => left.is_pure() && right.is_pure(),
|
||||
Expression::Unary { operand, .. } => operand.is_pure(),
|
||||
Expression::UnaryPostfix { operand, .. } => operand.is_pure(),
|
||||
Expression::Empty => true,
|
||||
Expression::Variable { .. } => true,
|
||||
Expression::TypeCast { expr, .. } => expr.is_pure(),
|
||||
Expression::IndexAccess { expr, index, .. } => {
|
||||
expr.is_pure() && index.is_pure()
|
||||
}
|
||||
Expression::MemberAccess { expr, .. } => expr.is_pure(),
|
||||
Expression::ArrayLiteral { elements, .. } => {
|
||||
elements.iter().all(|element| element.is_pure())
|
||||
}
|
||||
Expression::StructLiteral { fields, .. } => {
|
||||
fields.iter().all(|(_, expr)| expr.is_pure())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_id(&self) -> Result<TypeId, CompilerError> {
|
||||
match self {
|
||||
Expression::Number(
|
||||
Number::Signed(_, type_id) | Number::Unsigned(_, type_id),
|
||||
) => type_id.clone().ok_or(CompilerError::UnknownType),
|
||||
Expression::StringLiteral(_) => Ok(TypeId::Ptr(Box::new(TypeId::Char))),
|
||||
Expression::CharLiteral(_) => Ok(TypeId::Char),
|
||||
Expression::Call { type_id, .. } => {
|
||||
type_id.clone().ok_or(CompilerError::UnknownType)
|
||||
}
|
||||
Expression::Binary { type_id, .. } => {
|
||||
type_id.clone().ok_or(CompilerError::UnknownType)
|
||||
}
|
||||
Expression::Unary { type_id, .. } => {
|
||||
type_id.clone().ok_or(CompilerError::UnknownType)
|
||||
}
|
||||
Expression::UnaryPostfix { type_id, .. } => {
|
||||
type_id.clone().ok_or(CompilerError::UnknownType)
|
||||
}
|
||||
Expression::Empty => Ok(TypeId::Void),
|
||||
Expression::Variable { expr_type, .. } => {
|
||||
expr_type.clone().ok_or(CompilerError::UnknownType)
|
||||
}
|
||||
Expression::TypeCast { type_id, .. } => {
|
||||
type_id.clone().ok_or(CompilerError::UnknownType)
|
||||
}
|
||||
Expression::IndexAccess { expr, .. } => expr.type_id(),
|
||||
Expression::MemberAccess { expr, .. } => expr.type_id(),
|
||||
Expression::ArrayLiteral { elements, .. } => {
|
||||
let element_type = elements
|
||||
.first()
|
||||
.map_or(TypeId::Void, |e| e.type_id().unwrap_or(TypeId::Void));
|
||||
Ok(TypeId::Array {
|
||||
r#type: Box::new(element_type),
|
||||
size: elements.len(),
|
||||
})
|
||||
}
|
||||
Expression::StructLiteral { name, fields, .. } => {
|
||||
let fields = fields
|
||||
.iter()
|
||||
.map(|(_, expr)| expr.type_id())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(TypeId::Struct {
|
||||
name: name.clone(),
|
||||
fields,
|
||||
generics: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum AssignmentOperator {
|
||||
Assign,
|
||||
AddAssign,
|
||||
SubAssign,
|
||||
MulAssign,
|
||||
DivAssign,
|
||||
ModAssign,
|
||||
AndAssign,
|
||||
OrAssign,
|
||||
XorAssign,
|
||||
LeftShiftAssign,
|
||||
RightShiftAssign,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum BinaryOperator {
|
||||
// arithmetic
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
Mod,
|
||||
|
||||
// comparison
|
||||
Equal,
|
||||
NotEqual,
|
||||
LessThan,
|
||||
GreaterThan,
|
||||
LessOrEqual,
|
||||
GreaterOrEqual,
|
||||
|
||||
// bitwise
|
||||
BitwiseAnd,
|
||||
BitwiseOr,
|
||||
BitwiseXor,
|
||||
|
||||
// logical
|
||||
LogicalAnd,
|
||||
LogicalOr,
|
||||
|
||||
// shift
|
||||
LeftShift,
|
||||
RightShift,
|
||||
}
|
||||
|
||||
impl fmt::Display for BinaryOperator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Add => write!(f, "+"),
|
||||
Self::Sub => write!(f, "-"),
|
||||
Self::Mul => write!(f, "*"),
|
||||
Self::Div => write!(f, "/"),
|
||||
Self::Mod => write!(f, "%"),
|
||||
Self::Equal => write!(f, "=="),
|
||||
Self::NotEqual => write!(f, "!="),
|
||||
Self::LessThan => write!(f, "<"),
|
||||
Self::GreaterThan => write!(f, ">"),
|
||||
Self::LessOrEqual => write!(f, "<="),
|
||||
Self::GreaterOrEqual => write!(f, ">="),
|
||||
Self::BitwiseAnd => write!(f, "&"),
|
||||
Self::BitwiseOr => write!(f, "|"),
|
||||
Self::BitwiseXor => write!(f, "^"),
|
||||
Self::LogicalAnd => write!(f, "&&"),
|
||||
Self::LogicalOr => write!(f, "||"),
|
||||
Self::LeftShift => write!(f, "<<"),
|
||||
Self::RightShift => write!(f, ">>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum UnaryOperator {
|
||||
Plus,
|
||||
Minus,
|
||||
AddressOf,
|
||||
Dereference,
|
||||
BitwiseNot,
|
||||
LogicalNot,
|
||||
Increment,
|
||||
Decrement,
|
||||
SizeOf,
|
||||
}
|
||||
|
||||
impl fmt::Display for UnaryOperator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Increment => write!(f, "++"),
|
||||
Self::Decrement => write!(f, "--"),
|
||||
Self::Plus => write!(f, "+"),
|
||||
Self::Minus => write!(f, "-"),
|
||||
Self::Dereference => write!(f, "*"),
|
||||
Self::AddressOf => write!(f, "&"),
|
||||
Self::BitwiseNot => write!(f, "~"),
|
||||
Self::LogicalNot => write!(f, "!"),
|
||||
Self::SizeOf => write!(f, "sizeof"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
#[must_use]
|
||||
pub fn build(src: &str) -> String {
|
||||
parse(src).join("\n")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub fn parse(src: &str) -> Vec<String> {
|
||||
let stack = "0x10000";
|
||||
let acc = "acc";
|
||||
let rga = "rga";
|
||||
|
||||
let bpr = "bpr";
|
||||
let spr = "spr";
|
||||
let mut instrs = Vec::<String>::new();
|
||||
|
||||
// Define symbols
|
||||
let print_start = "print";
|
||||
|
||||
let tokens = lex(src);
|
||||
|
||||
let mut idstack = Vec::<u32>::new();
|
||||
|
||||
// set up a stack
|
||||
instrs.push(format!("\tlwi {}, {}", stack, bpr));
|
||||
instrs.push(format!("\tmov {}, {}", bpr, spr));
|
||||
// set up the data pointer
|
||||
instrs.push(format!("{}: \t lwi 0x30000, {}", "main", rga));
|
||||
|
||||
for (id, tok) in tokens.iter().enumerate() {
|
||||
match tok {
|
||||
BfToken::Inc => {
|
||||
instrs.push(format!("\tinc {}", acc));
|
||||
}
|
||||
BfToken::Dec => {
|
||||
instrs.push(format!("\tdec {}", acc));
|
||||
}
|
||||
BfToken::IncPtr => {
|
||||
instrs.push(format!("\tstw {}, {}, 0", acc, rga));
|
||||
instrs.push(format!("\taddi {}, 4, {}", rga, rga));
|
||||
instrs.push(format!("\tlwd {}, {}, 0", rga, acc));
|
||||
}
|
||||
BfToken::DecPtr => {
|
||||
instrs.push(format!("\tstw {}, {}, 0", acc, rga));
|
||||
instrs.push(format!("\tsubi {}, 4, {}", rga, rga));
|
||||
instrs.push(format!("\tlwd {}, {}, 0", rga, acc));
|
||||
}
|
||||
BfToken::Out => {
|
||||
instrs.push(format!("\tpush {}", acc));
|
||||
instrs.push(format!("\tcall {}", print_start));
|
||||
instrs.push(format!("\tpop zero"));
|
||||
}
|
||||
BfToken::In => {
|
||||
instrs.push(format!("\tlwd 0x40000, {}, 0", acc));
|
||||
}
|
||||
BfToken::Forward => {
|
||||
let loop_start = format!("loop_start_{}", id);
|
||||
let loop_end = format!("loop_end_{}", id);
|
||||
idstack.push(id as u32);
|
||||
instrs.push(format!("\tcmp {}, zero", acc));
|
||||
instrs.push(format!("\tjeq {}, zero", loop_end));
|
||||
instrs.push(format!("{}: \tnop", loop_start));
|
||||
}
|
||||
BfToken::Back => {
|
||||
if let Some(start_id) = idstack.pop() {
|
||||
let loop_start = format!("loop_start_{}", start_id);
|
||||
let loop_end = format!("loop_end_{}", start_id);
|
||||
instrs.push(format!("\tcmp {}, zero", acc));
|
||||
instrs.push(format!("\tjne {}, zero", loop_start));
|
||||
instrs.push(format!("{}: \tnop", loop_end));
|
||||
} else {
|
||||
eprintln!("Warning: Unmatched ']' at position {}", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instrs.push("\thlt".to_string());
|
||||
|
||||
insert_lib(&mut instrs);
|
||||
|
||||
instrs
|
||||
}
|
||||
|
||||
fn insert_lib(instrs: &mut Vec<String>) {
|
||||
let bpr = "bpr";
|
||||
let spr = "spr";
|
||||
let rg0 = "rg0";
|
||||
let rg1 = "rg1";
|
||||
|
||||
let print_start = "print";
|
||||
let current = "current";
|
||||
instrs.push(format!("\tdw {}, 0x20000", current));
|
||||
instrs.push(format!("{}: \tpush {}", print_start, bpr));
|
||||
instrs.push(format!("\tmov {}, {}", spr, bpr));
|
||||
instrs.push(format!("\tlwd {}, {}, 8", bpr, rg0));
|
||||
instrs.push(format!("\tlwd {}, {}, 0", current, rg1));
|
||||
instrs.push(format!("\tstb {}, {}, 0", rg0, rg1));
|
||||
instrs.push(format!("\taddi {}, 1, {}", rg1, rg1));
|
||||
instrs.push(format!("\tstw {}, {}, 0", rg1, current));
|
||||
instrs.push(format!("\tmov {}, {}", bpr, spr));
|
||||
instrs.push(format!("\tpop {}", bpr));
|
||||
instrs.push("\treturn".to_string());
|
||||
}
|
||||
|
||||
enum BfToken {
|
||||
Inc,
|
||||
Dec,
|
||||
IncPtr,
|
||||
DecPtr,
|
||||
Out,
|
||||
In,
|
||||
Forward,
|
||||
Back,
|
||||
}
|
||||
|
||||
fn lex(src: &str) -> Vec<BfToken> {
|
||||
src.chars()
|
||||
.filter_map(|c| match c {
|
||||
'+' => Some(BfToken::Inc),
|
||||
'-' => Some(BfToken::Dec),
|
||||
'>' => Some(BfToken::IncPtr),
|
||||
'<' => Some(BfToken::DecPtr),
|
||||
'.' => Some(BfToken::Out),
|
||||
',' => Some(BfToken::In),
|
||||
'[' => Some(BfToken::Forward),
|
||||
']' => Some(BfToken::Back),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn _create_symbol(id: u32) -> String {
|
||||
format!("label_{}", id)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use crate::model::CompilerError;
|
||||
|
||||
pub mod brainf;
|
||||
|
||||
pub fn build_specialised(ext: &str, data: &str) -> Option<Result<String, CompilerError>> {
|
||||
match ext {
|
||||
"bf" => {
|
||||
let res = brainf::build(data);
|
||||
Some(Ok(res))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "common"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,54 @@
|
||||
use std::{
|
||||
fmt,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BuildError {
|
||||
IoError(String),
|
||||
Generic(String),
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for BuildError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
Self::IoError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<dyn std::error::Error>> for BuildError {
|
||||
fn from(err: Box<dyn std::error::Error>) -> Self {
|
||||
Self::Generic(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BuildError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::IoError(err) => write!(f, "IO Error: {err}"),
|
||||
Self::Generic(err) => write!(f, "Generic Error: {err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Builder {
|
||||
type Output: Clone + std::convert::AsRef<[u8]>;
|
||||
|
||||
fn new(src_path: impl Into<PathBuf>) -> Self;
|
||||
|
||||
// starts compilation
|
||||
fn start(&mut self);
|
||||
|
||||
// non-blocking function, returns output if completed
|
||||
fn poll(&mut self) -> Option<Result<Self::Output, BuildError>>;
|
||||
|
||||
// blocking function, returns output when completed.
|
||||
fn output(&mut self) -> Result<Self::Output, BuildError>;
|
||||
|
||||
fn write_result(&mut self, path: impl AsRef<Path>) -> Result<(), BuildError> {
|
||||
let output = self.output()?;
|
||||
std::fs::write(path.as_ref(), output)
|
||||
.map_err(|e| BuildError::IoError(e.to_string()))
|
||||
}
|
||||
|
||||
fn logs(&self) -> Vec<String>;
|
||||
}
|
||||
@@ -0,0 +1,513 @@
|
||||
use crate::{instructions::encode::Encode, prelude::*};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Interrupt {
|
||||
Software(u8),
|
||||
Breakpoint,
|
||||
HardFault,
|
||||
}
|
||||
|
||||
pub type Address = u32;
|
||||
|
||||
impl Interrupt {
|
||||
// someone tell clippy to stfu.
|
||||
#[allow(clippy::must_use_candidate)]
|
||||
pub const fn as_u8(self) -> u8 {
|
||||
match self {
|
||||
Self::Breakpoint => 0,
|
||||
Self::HardFault => 1,
|
||||
Self::Software(code) => code,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This should be TryFrom.
|
||||
impl From<u8> for Interrupt {
|
||||
#[allow(unreachable_code)]
|
||||
fn from(code: u8) -> Self {
|
||||
match code {
|
||||
0 => Self::Breakpoint,
|
||||
1 => Self::HardFault,
|
||||
_ => Self::Software(code),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether an [`Instruction`] is an I-type or R-type instruction.
|
||||
#[non_exhaustive]
|
||||
pub enum InstructionType {
|
||||
Register,
|
||||
Immediate,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum Register {
|
||||
// general purpose registers
|
||||
Rg0,
|
||||
Rg1,
|
||||
Rg2,
|
||||
Rg3,
|
||||
Rg4,
|
||||
Rg5,
|
||||
Rg6,
|
||||
Rg7,
|
||||
Rg8,
|
||||
Rg9,
|
||||
Rga,
|
||||
Rgb,
|
||||
Rgc,
|
||||
Rgd,
|
||||
Rge,
|
||||
Rgf,
|
||||
|
||||
// special purpose registers
|
||||
Acc,
|
||||
Spr,
|
||||
Bpr,
|
||||
Ret,
|
||||
Idr,
|
||||
Mmr,
|
||||
Zero,
|
||||
|
||||
#[default]
|
||||
Null, // Invalid - Triggers a fault if accessed
|
||||
|
||||
// system registers - can't be written to by instructions.
|
||||
Mar,
|
||||
Mdr,
|
||||
Sts,
|
||||
Cir,
|
||||
Pcx,
|
||||
}
|
||||
|
||||
impl Register {
|
||||
// this is here so clippy shuts up about the must_use tag.
|
||||
#[allow(clippy::must_use_candidate)]
|
||||
pub fn general() -> Vec<Self> {
|
||||
vec![
|
||||
Self::Rg0,
|
||||
Self::Rg1,
|
||||
Self::Rg2,
|
||||
Self::Rg3,
|
||||
Self::Rg4,
|
||||
Self::Rg5,
|
||||
Self::Rg6,
|
||||
Self::Rg7,
|
||||
Self::Rg8,
|
||||
Self::Rg9,
|
||||
Self::Rga,
|
||||
Self::Rgb,
|
||||
Self::Rgc,
|
||||
Self::Rgd,
|
||||
Self::Rge,
|
||||
Self::Rgf,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Register {
|
||||
type Error = RegisterParseError;
|
||||
|
||||
fn try_from(idx: u8) -> Result<Self, Self::Error> {
|
||||
if idx > 0x1C {
|
||||
return Err(RegisterParseError::InvalidIndex(idx));
|
||||
}
|
||||
|
||||
Ok(match idx {
|
||||
// System registers are not indexable in the reg file so they cannot be
|
||||
// modified by instructions.
|
||||
0x0 => Self::Rg0,
|
||||
0x1 => Self::Rg1,
|
||||
0x2 => Self::Rg2,
|
||||
0x3 => Self::Rg3,
|
||||
0x4 => Self::Rg4,
|
||||
0x5 => Self::Rg5,
|
||||
0x6 => Self::Rg6,
|
||||
0x7 => Self::Rg7,
|
||||
0x8 => Self::Rg8,
|
||||
0x9 => Self::Rg9,
|
||||
0xA => Self::Rga,
|
||||
0xB => Self::Rgb,
|
||||
0xC => Self::Rgc,
|
||||
0xD => Self::Rgd,
|
||||
0xE => Self::Rge,
|
||||
0xF => Self::Rgf,
|
||||
0x10 => Self::Acc,
|
||||
0x11 => Self::Spr,
|
||||
0x12 => Self::Bpr,
|
||||
0x13 => Self::Ret,
|
||||
0x14 => Self::Idr,
|
||||
0x15 => Self::Mmr,
|
||||
0x16 => Self::Zero,
|
||||
0x17 => Self::Null,
|
||||
0x18 => Self::Mar,
|
||||
0x19 => Self::Mdr,
|
||||
0x1A => Self::Sts,
|
||||
0x1B => Self::Cir,
|
||||
0x1C => Self::Pcx,
|
||||
_ => unreachable!("This is already checked for in top `if` branch."),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Register {
|
||||
type Error = RegisterParseError;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
match value.to_lowercase().as_str() {
|
||||
"rg0" => Ok(Self::Rg0),
|
||||
"rg1" => Ok(Self::Rg1),
|
||||
"rg2" => Ok(Self::Rg2),
|
||||
"rg3" => Ok(Self::Rg3),
|
||||
"rg4" => Ok(Self::Rg4),
|
||||
"rg5" => Ok(Self::Rg5),
|
||||
"rg6" => Ok(Self::Rg6),
|
||||
"rg7" => Ok(Self::Rg7),
|
||||
"rg8" => Ok(Self::Rg8),
|
||||
"rg9" => Ok(Self::Rg9),
|
||||
"rga" => Ok(Self::Rga),
|
||||
"rgb" => Ok(Self::Rgb),
|
||||
"rgc" => Ok(Self::Rgc),
|
||||
"rgd" => Ok(Self::Rgd),
|
||||
"rge" => Ok(Self::Rge),
|
||||
"rgf" => Ok(Self::Rgf),
|
||||
"acc" => Ok(Self::Acc),
|
||||
"spr" => Ok(Self::Spr),
|
||||
"bpr" => Ok(Self::Bpr),
|
||||
"ret" => Ok(Self::Ret),
|
||||
"idr" => Ok(Self::Idr),
|
||||
"mmr" => Ok(Self::Mmr),
|
||||
"zero" => Ok(Self::Zero),
|
||||
"null" => Ok(Self::Null),
|
||||
"pcx" => Ok(Self::Pcx),
|
||||
_ => Err(RegisterParseError::InvalidName(value.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Register {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Rg0 => write!(f, "rg0"),
|
||||
Self::Rg1 => write!(f, "rg1"),
|
||||
Self::Rg2 => write!(f, "rg2"),
|
||||
Self::Rg3 => write!(f, "rg3"),
|
||||
Self::Rg4 => write!(f, "rg4"),
|
||||
Self::Rg5 => write!(f, "rg5"),
|
||||
Self::Rg6 => write!(f, "rg6"),
|
||||
Self::Rg7 => write!(f, "rg7"),
|
||||
Self::Rg8 => write!(f, "rg8"),
|
||||
Self::Rg9 => write!(f, "rg9"),
|
||||
Self::Rga => write!(f, "rga"),
|
||||
Self::Rgb => write!(f, "rgb"),
|
||||
Self::Rgc => write!(f, "rgc"),
|
||||
Self::Rgd => write!(f, "rgd"),
|
||||
Self::Rge => write!(f, "rge"),
|
||||
Self::Rgf => write!(f, "rgf"),
|
||||
Self::Acc => write!(f, "acc"),
|
||||
Self::Spr => write!(f, "spr"),
|
||||
Self::Bpr => write!(f, "bpr"),
|
||||
Self::Ret => write!(f, "ret"),
|
||||
Self::Idr => write!(f, "idr"),
|
||||
Self::Mmr => write!(f, "mmr"),
|
||||
Self::Zero => write!(f, "zero"),
|
||||
Self::Null => write!(f, "null"),
|
||||
Self::Mar => write!(f, "mar"),
|
||||
Self::Mdr => write!(f, "mdr"),
|
||||
Self::Sts => write!(f, "sts"),
|
||||
Self::Cir => write!(f, "cir"),
|
||||
Self::Pcx => write!(f, "pcx"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq)]
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
/// A list of all current instructions in the DSA.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This is subject to change and is therefore marked non exhaustive.
|
||||
pub enum Instruction {
|
||||
// No-op
|
||||
Nop = 0x0,
|
||||
|
||||
// Data transfer instructions
|
||||
Mov(args::RTypeArgs) = 0x1,
|
||||
MovSigned(args::RTypeArgs) = 0x2,
|
||||
|
||||
LoadByte(args::ITypeArgs) = 0x3,
|
||||
LoadByteSigned(args::ITypeArgs) = 0x4,
|
||||
LoadHalfword(args::ITypeArgs) = 0x5,
|
||||
LoadHalfwordSigned(args::ITypeArgs) = 0x6,
|
||||
LoadWord(args::ITypeArgs) = 0x7,
|
||||
|
||||
StoreByte(args::ITypeArgs) = 0x8,
|
||||
StoreHalfword(args::ITypeArgs) = 0x9,
|
||||
StoreWord(args::ITypeArgs) = 0xA,
|
||||
|
||||
LoadLowerImmediate(args::ITypeArgs) = 0xB,
|
||||
LoadUpperImmediate(args::ITypeArgs) = 0xC,
|
||||
|
||||
// Jump Instructions
|
||||
Jump(args::ITypeArgs) = 0xD,
|
||||
JumpEq(args::ITypeArgs) = 0xE,
|
||||
JumpNeq(args::ITypeArgs) = 0xF,
|
||||
JumpGt(args::ITypeArgs) = 0x10,
|
||||
JumpGe(args::ITypeArgs) = 0x11,
|
||||
JumpLt(args::ITypeArgs) = 0x12,
|
||||
JumpLe(args::ITypeArgs) = 0x13,
|
||||
|
||||
// Comparison
|
||||
Compare(args::RTypeArgs) = 0x14,
|
||||
|
||||
// Arithmetic
|
||||
Add(args::RTypeArgs) = 0x19,
|
||||
Sub(args::RTypeArgs) = 0x1A,
|
||||
Increment(args::RTypeArgs) = 0x15,
|
||||
Decrement(args::RTypeArgs) = 0x16,
|
||||
ShiftLeft(args::RTypeArgs) = 0x17,
|
||||
ShiftRight(args::RTypeArgs) = 0x18,
|
||||
|
||||
// Logical
|
||||
And(args::RTypeArgs) = 0x1B,
|
||||
Or(args::RTypeArgs) = 0x1C,
|
||||
Not(args::RTypeArgs) = 0x1D,
|
||||
Xor(args::RTypeArgs) = 0x1E,
|
||||
Nand(args::RTypeArgs) = 0x1F,
|
||||
Nor(args::RTypeArgs) = 0x20,
|
||||
Xnor(args::RTypeArgs) = 0x21,
|
||||
|
||||
// Misc
|
||||
Interrupt(Interrupt) = 0x22,
|
||||
IntReturn = 0x23,
|
||||
Halt = 0x24,
|
||||
|
||||
// Immediate Arithmetic
|
||||
AddImmediate(args::ITypeArgs) = 0x25,
|
||||
SubImmediate(args::ITypeArgs) = 0x26,
|
||||
|
||||
// Fake Instructions
|
||||
Data(u32) = 0x3E,
|
||||
Segment(u32) = 0x3F,
|
||||
}
|
||||
|
||||
impl PartialEq for Instruction {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.encode() == other.encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
/// Returns the opcode of an instruction.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// The top two bits shall be 0, opcodes are 6-bits long.
|
||||
#[must_use]
|
||||
pub const fn opcode(&self) -> u8 {
|
||||
unsafe { *std::ptr::from_ref::<Self>(self).cast::<u8>() }
|
||||
}
|
||||
|
||||
/// Encodes an [`Instruction`] into a word.
|
||||
#[must_use]
|
||||
pub fn encode(&self) -> u32 {
|
||||
Encode::encode(*self, self.opcode())
|
||||
}
|
||||
|
||||
/// Decodes an [`Instruction`] from a word `data`.
|
||||
pub fn decode(data: u32) -> Result<Self, InstructionDecodeError> {
|
||||
data.try_into()
|
||||
}
|
||||
|
||||
/// Returns the mnemonic for a given [`Instruction`].
|
||||
#[must_use]
|
||||
pub const fn mnemonic(self) -> &'static str {
|
||||
match self {
|
||||
Self::Add(_) => "add",
|
||||
Self::Sub(_) => "sub",
|
||||
Self::Increment(_) => "inc",
|
||||
Self::Decrement(_) => "dec",
|
||||
Self::Compare(_) => "cmp",
|
||||
Self::Halt => "hlt",
|
||||
Self::And(_) => "and",
|
||||
Self::IntReturn => "intr",
|
||||
Self::Interrupt(_) => "int",
|
||||
Self::Jump(_) => "jmp",
|
||||
Self::JumpEq(_) => "jeq",
|
||||
Self::JumpNeq(_) => "jneq",
|
||||
Self::JumpGt(_) => "jgt",
|
||||
Self::JumpGe(_) => "jge",
|
||||
Self::JumpLt(_) => "jlt",
|
||||
Self::JumpLe(_) => "jle",
|
||||
Self::Mov(_) => "mov",
|
||||
Self::MovSigned(_) => "movs",
|
||||
Self::LoadByte(_) => "ldb",
|
||||
Self::LoadByteSigned(_) => "ldbs",
|
||||
Self::LoadHalfword(_) => "ldh",
|
||||
Self::LoadHalfwordSigned(_) => "ldhs",
|
||||
Self::LoadWord(_) => "ldw",
|
||||
Self::StoreByte(_) => "stb",
|
||||
Self::StoreHalfword(_) => "sth",
|
||||
Self::StoreWord(_) => "stw",
|
||||
Self::LoadLowerImmediate(_) => "lli",
|
||||
Self::LoadUpperImmediate(_) => "lui",
|
||||
Self::ShiftLeft(_) => "shl",
|
||||
Self::ShiftRight(_) => "shr",
|
||||
Self::Or(_) => "or",
|
||||
Self::Not(_) => "not",
|
||||
Self::Nop => "nop",
|
||||
Self::Xor(_) => "xor",
|
||||
Self::Nand(_) => "nand",
|
||||
Self::Nor(_) => "nor",
|
||||
Self::Xnor(_) => "xnor",
|
||||
Self::Data(_) => "data",
|
||||
Self::AddImmediate(_) => "addi",
|
||||
Self::SubImmediate(_) => "subi",
|
||||
Self::Segment(_) => "[SEGMENT]",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`InstructionType`] for the given [`Instruction`].
|
||||
#[must_use]
|
||||
pub const fn instruction_type(self) -> InstructionType {
|
||||
Self::instruction_type_from_opcode(self.opcode())
|
||||
}
|
||||
|
||||
/// Returns the [`InstructionType`] for the given `opcode`.
|
||||
#[must_use]
|
||||
pub const fn instruction_type_from_opcode(opcode: u8) -> InstructionType {
|
||||
match opcode {
|
||||
0x3..=0x13 => InstructionType::Immediate,
|
||||
_ => InstructionType::Register,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Instruction decoding logic goes here.
|
||||
impl std::fmt::Display for Instruction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.mnemonic())?;
|
||||
|
||||
match self {
|
||||
Self::Mov(args) | Self::MovSigned(args) => {
|
||||
write!(f, " {}, {}", args.sr1, args.dr)
|
||||
}
|
||||
Self::LoadByte(args)
|
||||
| Self::LoadByteSigned(args)
|
||||
| Self::LoadHalfword(args)
|
||||
| Self::LoadHalfwordSigned(args)
|
||||
| Self::LoadWord(args)
|
||||
| Self::StoreByte(args)
|
||||
| Self::StoreHalfword(args)
|
||||
| Self::StoreWord(args) => {
|
||||
write!(
|
||||
f,
|
||||
" {}({:x}/{}), {}",
|
||||
args.r1, args.immediate, args.immediate, args.r2
|
||||
)
|
||||
}
|
||||
Self::Jump(args)
|
||||
| Self::JumpEq(args)
|
||||
| Self::JumpNeq(args)
|
||||
| Self::JumpGt(args)
|
||||
| Self::JumpGe(args)
|
||||
| Self::JumpLt(args)
|
||||
| Self::JumpLe(args) => {
|
||||
write!(f, " 0x{:x}/{}({})", args.immediate, args.immediate, args.r1)
|
||||
}
|
||||
Self::LoadLowerImmediate(args) | Self::LoadUpperImmediate(args) => {
|
||||
write!(f, " 0x{:x}, {}, {}", args.immediate, args.r1, args.r2)
|
||||
}
|
||||
Self::Compare(args) | Self::Not(args) => {
|
||||
write!(f, " {}, {}", args.sr1, args.sr2)
|
||||
}
|
||||
|
||||
Self::Add(args)
|
||||
| Self::Sub(args)
|
||||
| Self::Xor(args)
|
||||
| Self::Nand(args)
|
||||
| Self::Nor(args)
|
||||
| Self::Xnor(args)
|
||||
| Self::ShiftLeft(args)
|
||||
| Self::ShiftRight(args)
|
||||
| Self::And(args)
|
||||
| Self::Or(args) => {
|
||||
write!(f, " {}, {}, {}", args.sr1, args.sr2, args.dr)
|
||||
}
|
||||
|
||||
Self::AddImmediate(args) | Self::SubImmediate(args) => {
|
||||
write!(f, " {}, {}, {}", args.r1, args.immediate, args.r2)
|
||||
}
|
||||
|
||||
Self::Increment(a) | Self::Decrement(a) => write!(f, " {}", a.sr1),
|
||||
Self::Interrupt(a) => write!(f, " {}", a.as_u8()),
|
||||
Self::Data(a) => write!(f, " {a}"),
|
||||
Self::Segment(x) => write!(f, " [SEGMENT {x}]"),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for Instruction {
|
||||
type Error = InstructionDecodeError;
|
||||
|
||||
/// Instruction decoding can be using using [`Instruction::try_from`]
|
||||
fn try_from(data: u32) -> Result<Self, Self::Error> {
|
||||
// Pull the opcode out so we can parse it correctly.
|
||||
let opcode = ((data >> 26) & 0x3F) as u8;
|
||||
|
||||
match opcode {
|
||||
0x0 => Ok(Self::Nop),
|
||||
0x1 => Ok(Self::Mov(RTypeArgs::try_from(data)?)),
|
||||
0x2 => Ok(Self::MovSigned(RTypeArgs::try_from(data)?)),
|
||||
0x3 => Ok(Self::LoadByte(ITypeArgs::try_from(data)?)),
|
||||
0x4 => Ok(Self::LoadByteSigned(ITypeArgs::try_from(data)?)),
|
||||
0x5 => Ok(Self::LoadHalfword(ITypeArgs::try_from(data)?)),
|
||||
0x6 => Ok(Self::LoadHalfwordSigned(ITypeArgs::try_from(data)?)),
|
||||
0x7 => Ok(Self::LoadWord(ITypeArgs::try_from(data)?)),
|
||||
0x8 => Ok(Self::StoreByte(ITypeArgs::try_from(data)?)),
|
||||
0x9 => Ok(Self::StoreHalfword(ITypeArgs::try_from(data)?)),
|
||||
0xA => Ok(Self::StoreWord(ITypeArgs::try_from(data)?)),
|
||||
0xB => Ok(Self::LoadLowerImmediate(ITypeArgs::try_from(data)?)),
|
||||
0xC => Ok(Self::LoadUpperImmediate(ITypeArgs::try_from(data)?)),
|
||||
0xD => Ok(Self::Jump(ITypeArgs::try_from(data)?)),
|
||||
0xE => Ok(Self::JumpEq(ITypeArgs::try_from(data)?)),
|
||||
0xF => Ok(Self::JumpNeq(ITypeArgs::try_from(data)?)),
|
||||
0x10 => Ok(Self::JumpGt(ITypeArgs::try_from(data)?)),
|
||||
0x11 => Ok(Self::JumpGe(ITypeArgs::try_from(data)?)),
|
||||
0x12 => Ok(Self::JumpLt(ITypeArgs::try_from(data)?)),
|
||||
0x13 => Ok(Self::JumpLe(ITypeArgs::try_from(data)?)),
|
||||
0x14 => Ok(Self::Compare(RTypeArgs::try_from(data)?)),
|
||||
0x15 => Ok(Self::Increment(RTypeArgs::try_from(data)?)),
|
||||
0x16 => Ok(Self::Decrement(RTypeArgs::try_from(data)?)),
|
||||
0x17 => Ok(Self::ShiftLeft(RTypeArgs::try_from(data)?)),
|
||||
0x18 => Ok(Self::ShiftRight(RTypeArgs::try_from(data)?)),
|
||||
0x19 => Ok(Self::Add(RTypeArgs::try_from(data)?)),
|
||||
0x1A => Ok(Self::Sub(RTypeArgs::try_from(data)?)),
|
||||
0x1B => Ok(Self::And(RTypeArgs::try_from(data)?)),
|
||||
0x1C => Ok(Self::Or(RTypeArgs::try_from(data)?)),
|
||||
0x1D => Ok(Self::Not(RTypeArgs::try_from(data)?)),
|
||||
0x1E => Ok(Self::Xor(RTypeArgs::try_from(data)?)),
|
||||
0x1F => Ok(Self::Nand(RTypeArgs::try_from(data)?)),
|
||||
0x20 => Ok(Self::Nor(RTypeArgs::try_from(data)?)),
|
||||
0x21 => Ok(Self::Xnor(RTypeArgs::try_from(data)?)),
|
||||
0x22 => Ok(Self::Interrupt(Interrupt::from((data & 0xFF) as u8))),
|
||||
0x23 => Ok(Self::IntReturn),
|
||||
0x24 => Ok(Self::Halt),
|
||||
0x25 => Ok(Self::AddImmediate(ITypeArgs::try_from(data)?)),
|
||||
0x26 => Ok(Self::SubImmediate(ITypeArgs::try_from(data)?)),
|
||||
0x3F => Ok(Self::Segment(u32::from(data as u8))),
|
||||
_ => Err(InstructionDecodeError::InvalidOpcode(opcode)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod args;
|
||||
mod encode;
|
||||
pub mod errors;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -0,0 +1,208 @@
|
||||
//! Various types of arguments that instructions can take, alongside encoding and decoding logic.
|
||||
|
||||
use crate::{
|
||||
instructions::{RegisterParseError, encode::Encode},
|
||||
prelude::Register,
|
||||
};
|
||||
|
||||
/// A list of errors that can be returned when decoding instruction arguments.
|
||||
#[derive(Debug)]
|
||||
pub enum ArgsDecodeError {
|
||||
/// The register was not valid.
|
||||
InvalidRegister(u8),
|
||||
}
|
||||
|
||||
impl From<RegisterParseError> for ArgsDecodeError {
|
||||
fn from(value: RegisterParseError) -> Self {
|
||||
match value {
|
||||
RegisterParseError::InvalidIndex(idx) => Self::InvalidRegister(idx),
|
||||
RegisterParseError::InvalidName(_) => Self::InvalidRegister(0xFF),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ArgsDecodeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InvalidRegister(idx) => {
|
||||
write!(f, "invalid register index, got {idx:x}")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ArgsDecodeError {}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// Used by instructions with 2 registers and an immediate argument.
|
||||
pub struct ITypeArgs {
|
||||
pub immediate: u16,
|
||||
pub r1: Register,
|
||||
/// May not actually be used by some instructions taking an immediate e.g. LUI. This is solved by making the constructor take Options.
|
||||
pub r2: Register,
|
||||
}
|
||||
|
||||
impl ITypeArgs {
|
||||
#[must_use]
|
||||
/// Creates a new [`ITypeArgs`]. If r1 or r2 is unset, they will be replaced with [`Register::NoReg`].
|
||||
pub fn new(immediate: u16, r1: Option<Register>, r2: Option<Register>) -> Self {
|
||||
let r1 = r1.unwrap_or_default();
|
||||
let r2 = r2.unwrap_or_default();
|
||||
|
||||
Self { immediate, r1, r2 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for ITypeArgs {
|
||||
/// Encodes an I-type instruction from its fields. These must have some unused high-order
|
||||
/// bits set to 0 else the bit shifting logic gets fucked.
|
||||
fn encode(self, opcode: u8) -> u32 {
|
||||
let opcode = u32::from(opcode);
|
||||
let r1 = self.r1 as u32;
|
||||
let dr = self.r2 as u32;
|
||||
let immediate = u32::from(self.immediate);
|
||||
|
||||
(opcode << 26) | (r1 << 21) | (dr << 16) | immediate
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for ITypeArgs {
|
||||
type Error = ArgsDecodeError;
|
||||
|
||||
fn try_from(data: u32) -> Result<Self, Self::Error> {
|
||||
let r1 = ((data >> 21) & 0x1F) as u8;
|
||||
let r2 = ((data >> 16) & 0x1F) as u8;
|
||||
let immediate = data as u16;
|
||||
|
||||
let r1 = r1.try_into()?;
|
||||
let r2 = r2.try_into()?;
|
||||
|
||||
Ok(Self { immediate, r1, r2 })
|
||||
}
|
||||
}
|
||||
|
||||
/// Used by instructions not using immediates (besides 5 bit shift values).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct RTypeArgs {
|
||||
pub sr1: Register,
|
||||
pub sr2: Register,
|
||||
pub dr: Register,
|
||||
/// 5 bit shift amount.
|
||||
pub shamt: u8,
|
||||
}
|
||||
|
||||
impl RTypeArgs {
|
||||
#[must_use]
|
||||
/// Creates a new [`RTypeArgs`]. If any registers are unset, they will be replaced with [`Register::NoReg`]. If `shamt` is unset, it will be set to 0.
|
||||
pub fn new(
|
||||
sr1: Option<Register>,
|
||||
sr2: Option<Register>,
|
||||
dr: Option<Register>,
|
||||
shamt: Option<u8>,
|
||||
) -> Self {
|
||||
let sr1 = sr1.unwrap_or_default();
|
||||
let shamt = shamt.unwrap_or_default();
|
||||
let sr2 = sr2.unwrap_or_default();
|
||||
let dr = dr.unwrap_or_default();
|
||||
|
||||
Self {
|
||||
sr1,
|
||||
sr2,
|
||||
dr,
|
||||
shamt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for RTypeArgs {
|
||||
/// Encodes an R-type instruction from its fields. These must have unused high-order
|
||||
/// bits set to 0 else the bit shifting logic is fucked.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `shamt`: The amount to shift value (used only in shift instructions, otherwise 0).
|
||||
fn encode(self, opcode: u8) -> u32 {
|
||||
let opcode = u32::from(opcode);
|
||||
let sr1 = self.sr1 as u32;
|
||||
let sr2 = self.sr2 as u32;
|
||||
let dr = self.dr as u32;
|
||||
let shamt = u32::from(self.shamt);
|
||||
|
||||
(opcode << 26) | (sr1 << 21) | (sr2 << 16) | (dr << 11) | (shamt << 6)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for RTypeArgs {
|
||||
type Error = ArgsDecodeError;
|
||||
|
||||
fn try_from(data: u32) -> Result<Self, Self::Error> {
|
||||
let sr1 = ((data >> 21) & 0x1F) as u8;
|
||||
let sr2 = ((data >> 16) & 0x1F) as u8;
|
||||
let dr = ((data >> 11) & 0x1F) as u8;
|
||||
let shamt = ((data >> 6) & 0x1F) as u8;
|
||||
|
||||
let sr1_reg = sr1.try_into()?;
|
||||
let sr2_reg = sr2.try_into()?;
|
||||
let dr_reg = dr.try_into()?;
|
||||
|
||||
Ok(Self {
|
||||
sr1: sr1_reg,
|
||||
sr2: sr2_reg,
|
||||
dr: dr_reg,
|
||||
shamt,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! args {
|
||||
// R-type arguments - allows omitting any field
|
||||
(R $(, $field:ident: $value:expr)* $(,)?) => {{
|
||||
let mut sr1: Option<Register> = None;
|
||||
let mut sr2: Option<Register> = None;
|
||||
let mut dr: Option<Register> = None;
|
||||
let mut shamt: Option<u8> = None;
|
||||
|
||||
$(
|
||||
args!(@assign_r_option sr1, sr2, dr, shamt, $field, $value);
|
||||
)*
|
||||
|
||||
RTypeArgs::new(sr1, sr2, dr, shamt)
|
||||
}};
|
||||
|
||||
// I-type arguments - requires immediate, allows omitting registers
|
||||
(I, immediate: $immediate:expr $(, $field:ident: $value:expr)* $(,)?) => {{
|
||||
let mut r1: Option<Register> = None;
|
||||
let mut r2: Option<Register> = None;
|
||||
|
||||
$(
|
||||
args!(@assign_i_option r1, r2, $field, $value);
|
||||
)*
|
||||
|
||||
ITypeArgs::new($immediate, r1, r2)
|
||||
}};
|
||||
|
||||
// Internal helpers (same as above for R-type)
|
||||
(@assign_r_option $sr1:ident, $sr2:ident, $dr:ident, $shamt:ident, sr1, $value:expr) => {
|
||||
$sr1 = Some($value);
|
||||
};
|
||||
(@assign_r_option $sr1:ident, $sr2:ident, $dr:ident, $shamt:ident, sr2, $value:expr) => {
|
||||
$sr2 = Some($value);
|
||||
};
|
||||
(@assign_r_option $sr1:ident, $sr2:ident, $dr:ident, $shamt:ident, dr, $value:expr) => {
|
||||
$dr = Some($value);
|
||||
};
|
||||
(@assign_r_option $sr1:ident, $sr2:ident, $dr:ident, $shamt:ident, shamt, $value:expr) => {
|
||||
$shamt = Some($value);
|
||||
};
|
||||
|
||||
// Internal helpers for I-type (without immediate handling)
|
||||
(@assign_i_option $r1:ident, $r2:ident, r1, $value:expr) => {
|
||||
$r1 = Some($value);
|
||||
};
|
||||
(@assign_i_option $r1:ident, $r2:ident, r2, $value:expr) => {
|
||||
$r2 = Some($value);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Not to be used directly, just call [`Instruction::encode`].
|
||||
pub trait Encode {
|
||||
fn encode(self, opcode: u8) -> u32;
|
||||
}
|
||||
|
||||
/// Encodes a zero argument instruction.
|
||||
fn encode_no_args(opcode: u8) -> u32 {
|
||||
let opcode = u32::from(opcode);
|
||||
let sr1 = Register::Null as u32;
|
||||
let sr2 = Register::Null as u32;
|
||||
let dr = Register::Null as u32;
|
||||
let shamt = 0;
|
||||
|
||||
(opcode << 26) | (sr1 << 21) | (sr2 << 16) | (dr << 11) | (shamt << 6)
|
||||
}
|
||||
|
||||
/// Expands to a match statement that calls encode on instructions that implement
|
||||
/// [`Encode`]:
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// ```rs
|
||||
/// encode_instruction!(self, with_args: [...], no_args: [...], special: [...] )
|
||||
/// ```
|
||||
macro_rules! encode_instruction {
|
||||
($self:expr, with_args: [$($variant:ident),+ $(,)?], no_args: [$($no_arg_variant:ident),* $(,)?] $(, special: [$($special:pat => $body:expr),* $(,)?])?) => {
|
||||
match $self {
|
||||
$(
|
||||
Instruction::$variant(args) => args.encode($self.opcode()),
|
||||
)+
|
||||
$(
|
||||
Instruction::$no_arg_variant => encode_no_args($self.opcode()),
|
||||
)*
|
||||
$($(
|
||||
$special => $body,
|
||||
)*)?
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl Encode for Instruction {
|
||||
fn encode(self, _: u8) -> u32 {
|
||||
encode_instruction!(
|
||||
self,
|
||||
with_args: [
|
||||
Mov, MovSigned, LoadByte, LoadByteSigned, LoadHalfword,
|
||||
LoadHalfwordSigned, LoadWord, StoreByte, StoreHalfword,
|
||||
StoreWord, LoadLowerImmediate, LoadUpperImmediate, Jump,
|
||||
JumpEq, JumpNeq, JumpGt, JumpGe, JumpLt, JumpLe, Compare,
|
||||
Add, Sub, Increment, Decrement, ShiftLeft, ShiftRight,
|
||||
And, Or, Not, Xor, Nand, Nor, Xnor, AddImmediate, SubImmediate
|
||||
],
|
||||
no_args: [Nop, IntReturn, Halt],
|
||||
special: [
|
||||
Self::Data(data) => data,
|
||||
Self::Interrupt(interrupt) => {
|
||||
let opcode = u32::from(self.opcode());
|
||||
(opcode << 26) | u32::from(interrupt.as_u8())
|
||||
},
|
||||
Self::Segment(segment) => {
|
||||
let opcode = u32::from(self.opcode());
|
||||
(opcode << 26) | u32::from(segment as u8)
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -0,0 +1,98 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_encode_nop() {
|
||||
let no_reg = Register::Null as u32;
|
||||
let no_op = u32::from(Instruction::Nop.opcode());
|
||||
|
||||
let expected = (no_op << 26) | (no_reg << 21) | (no_reg << 16) | (no_reg << 11);
|
||||
let got = Instruction::Nop.encode();
|
||||
|
||||
assert_eq!(expected, got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_mov() {
|
||||
let rg0 = Register::Rg0 as u32;
|
||||
let rg1 = Register::Rg1 as u32;
|
||||
let no_reg = Register::Null as u32;
|
||||
|
||||
let instruction = Instruction::Mov(RTypeArgs::new(
|
||||
Some(Register::Rg0),
|
||||
None,
|
||||
Some(Register::Rg1),
|
||||
None,
|
||||
));
|
||||
let mov = u32::from(instruction.opcode());
|
||||
|
||||
let expected = (mov << 26) | (rg0 << 21) | (no_reg << 16) | (rg1 << 11);
|
||||
let got = instruction.encode();
|
||||
|
||||
assert_eq!(expected, got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_load_byte() {
|
||||
let rg0 = Register::Rg0 as u32;
|
||||
let rg1 = Register::Rg1 as u32;
|
||||
let immediate = 100;
|
||||
|
||||
let instruction = Instruction::LoadByte(ITypeArgs::new(
|
||||
immediate,
|
||||
Some(Register::Rg0),
|
||||
Some(Register::Rg1),
|
||||
));
|
||||
let load_byte = u32::from(instruction.opcode());
|
||||
|
||||
let expected = (load_byte << 26) | (rg0 << 21) | (rg1 << 16) | u32::from(immediate);
|
||||
let got = instruction.encode();
|
||||
|
||||
assert_eq!(expected, got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_shift_left_shamt() {
|
||||
let rg0 = Register::Rg0 as u32;
|
||||
let no_reg = Register::Null as u32;
|
||||
|
||||
let shift_amount = 5;
|
||||
|
||||
let instruction = Instruction::ShiftLeft(RTypeArgs::new(
|
||||
Some(Register::Rg0),
|
||||
None,
|
||||
None,
|
||||
Some(shift_amount),
|
||||
));
|
||||
let shift_left = u32::from(instruction.opcode());
|
||||
|
||||
let expected = (shift_left << 26)
|
||||
| (rg0 << 21)
|
||||
| (no_reg << 16)
|
||||
| (no_reg << 11)
|
||||
| (u32::from(shift_amount) << 6);
|
||||
|
||||
let got = instruction.encode();
|
||||
|
||||
assert_eq!(expected, got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_shift_left_reg() {
|
||||
let rg0 = Register::Rg0 as u32;
|
||||
let rg1 = Register::Rg1 as u32;
|
||||
let no_reg = Register::Null as u32;
|
||||
|
||||
let instruction = Instruction::ShiftLeft(RTypeArgs::new(
|
||||
Some(Register::Rg0),
|
||||
Some(Register::Rg1),
|
||||
None,
|
||||
None,
|
||||
));
|
||||
let shift_left = u32::from(instruction.opcode());
|
||||
|
||||
let expected = (shift_left << 26) | (rg0 << 21) | (rg1 << 16) | (no_reg << 11);
|
||||
|
||||
let got = instruction.encode();
|
||||
|
||||
assert_eq!(expected, got);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//! All the errors that may be returned from [`instructions`].
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Error type for parsing register numbers.
|
||||
pub enum RegisterParseError {
|
||||
InvalidIndex(u8),
|
||||
InvalidName(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RegisterParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InvalidIndex(idx) => write!(f, "invalid index given ({idx})"),
|
||||
Self::InvalidName(name) => write!(f, "invalid name given ({name})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RegisterParseError {}
|
||||
|
||||
/// A list of errors that can be returned when decoding instructions.
|
||||
#[derive(Debug)]
|
||||
pub enum InstructionDecodeError {
|
||||
/// Some field was incorrect. Returns an error for debugging purposes.
|
||||
InvalidArgument(ArgsDecodeError),
|
||||
/// Some opcode was invalid. Returns the offending opcode for debugging purposes etc.
|
||||
InvalidOpcode(u8),
|
||||
}
|
||||
|
||||
impl From<ArgsDecodeError> for InstructionDecodeError {
|
||||
fn from(err: ArgsDecodeError) -> Self {
|
||||
Self::InvalidArgument(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for InstructionDecodeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InvalidOpcode(code) => write!(f, "invalid opcode, got {code:x}")?,
|
||||
Self::InvalidArgument(err) => write!(f, "invalid arguments, got an error {err}")?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for InstructionDecodeError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::InvalidArgument(err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
use crate::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_opcode_nop() {
|
||||
let instr = Instruction::Nop;
|
||||
assert_eq!(instr.opcode(), 0x0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opcode_data_transfer() {
|
||||
let args = RTypeArgs::new(None, None, None, None);
|
||||
assert_eq!(Instruction::Mov(args).opcode(), 0x1);
|
||||
assert_eq!(Instruction::MovSigned(args).opcode(), 0x2);
|
||||
|
||||
let iargs = ITypeArgs::new(0, None, None);
|
||||
assert_eq!(Instruction::LoadByte(iargs).opcode(), 0x3);
|
||||
assert_eq!(Instruction::LoadByteSigned(iargs).opcode(), 0x4);
|
||||
assert_eq!(Instruction::LoadHalfword(iargs).opcode(), 0x5);
|
||||
assert_eq!(Instruction::LoadHalfwordSigned(iargs).opcode(), 0x6);
|
||||
assert_eq!(Instruction::LoadWord(iargs).opcode(), 0x7);
|
||||
assert_eq!(Instruction::StoreByte(iargs).opcode(), 0x8);
|
||||
assert_eq!(Instruction::StoreHalfword(iargs).opcode(), 0x9);
|
||||
assert_eq!(Instruction::StoreWord(iargs).opcode(), 0xA);
|
||||
assert_eq!(Instruction::LoadLowerImmediate(iargs).opcode(), 0xB);
|
||||
assert_eq!(Instruction::LoadUpperImmediate(iargs).opcode(), 0xC);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opcode_jump_instructions() {
|
||||
let args = ITypeArgs::new(0, None, None);
|
||||
assert_eq!(Instruction::Jump(args).opcode(), 0xD);
|
||||
assert_eq!(Instruction::JumpEq(args).opcode(), 0xE);
|
||||
assert_eq!(Instruction::JumpNeq(args).opcode(), 0xF);
|
||||
assert_eq!(Instruction::JumpGt(args).opcode(), 0x10);
|
||||
assert_eq!(Instruction::JumpGe(args).opcode(), 0x11);
|
||||
assert_eq!(Instruction::JumpLt(args).opcode(), 0x12);
|
||||
assert_eq!(Instruction::JumpLe(args).opcode(), 0x13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opcode_arithmetic() {
|
||||
let args = RTypeArgs::new(None, None, None, None);
|
||||
assert_eq!(Instruction::Compare(args).opcode(), 0x14);
|
||||
assert_eq!(Instruction::Increment(args).opcode(), 0x15);
|
||||
assert_eq!(Instruction::Decrement(args).opcode(), 0x16);
|
||||
assert_eq!(Instruction::ShiftLeft(args).opcode(), 0x17);
|
||||
assert_eq!(Instruction::ShiftRight(args).opcode(), 0x18);
|
||||
assert_eq!(Instruction::Add(args).opcode(), 0x19);
|
||||
assert_eq!(Instruction::Sub(args).opcode(), 0x1A);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opcode_logical() {
|
||||
let args = RTypeArgs::new(None, None, None, None);
|
||||
assert_eq!(Instruction::And(args).opcode(), 0x1B);
|
||||
assert_eq!(Instruction::Or(args).opcode(), 0x1C);
|
||||
assert_eq!(Instruction::Not(args).opcode(), 0x1D);
|
||||
assert_eq!(Instruction::Xor(args).opcode(), 0x1E);
|
||||
assert_eq!(Instruction::Nand(args).opcode(), 0x1F);
|
||||
assert_eq!(Instruction::Nor(args).opcode(), 0x20);
|
||||
assert_eq!(Instruction::Xnor(args).opcode(), 0x21);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opcode_misc() {
|
||||
let interrupt = Interrupt::Software(5);
|
||||
assert_eq!(Instruction::Interrupt(interrupt).opcode(), 0x22);
|
||||
assert_eq!(Instruction::IntReturn.opcode(), 0x23);
|
||||
assert_eq!(Instruction::Halt.opcode(), 0x24);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opcode_with_different_args() {
|
||||
let args1 = RTypeArgs::new(
|
||||
Some(Register::Rg0),
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Rg2),
|
||||
Some(5),
|
||||
);
|
||||
let args2 = RTypeArgs::new(
|
||||
Some(Register::Acc),
|
||||
Some(Register::Spr),
|
||||
Some(Register::Bpr),
|
||||
Some(31),
|
||||
);
|
||||
|
||||
// Opcode should be the same regardless of arguments
|
||||
assert_eq!(
|
||||
Instruction::Add(args1).opcode(),
|
||||
Instruction::Add(args2).opcode()
|
||||
);
|
||||
assert_eq!(
|
||||
Instruction::Sub(args1).opcode(),
|
||||
Instruction::Sub(args2).opcode()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opcode_boundary_values() {
|
||||
// Test highest opcode value
|
||||
assert_eq!(Instruction::Halt.opcode(), 0x24);
|
||||
|
||||
// Test lowest opcode value
|
||||
assert_eq!(Instruction::Nop.opcode(), 0x0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_decode_nop() {
|
||||
let instr = Instruction::Nop;
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_decode_data_transfer() {
|
||||
let args = RTypeArgs::new(
|
||||
Some(Register::Rg0),
|
||||
Some(Register::Rg1),
|
||||
Some(Register::Rg2),
|
||||
Some(5),
|
||||
);
|
||||
let instr = Instruction::Mov(args);
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
|
||||
let iargs = ITypeArgs::new(100, Some(Register::Rg3), Some(Register::Rg4));
|
||||
let instr = Instruction::LoadWord(iargs);
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_decode_jump() {
|
||||
let args = ITypeArgs::new(200, Some(Register::Acc), Some(Register::Spr));
|
||||
let instr = Instruction::Jump(args);
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
|
||||
let instr = Instruction::JumpEq(args);
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_decode_arithmetic() {
|
||||
let args = RTypeArgs::new(
|
||||
Some(Register::Bpr),
|
||||
Some(Register::Rg7),
|
||||
Some(Register::Rgf),
|
||||
Some(31),
|
||||
);
|
||||
let instr = Instruction::Add(args);
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
|
||||
let instr = Instruction::Compare(args);
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_decode_logical() {
|
||||
let args = RTypeArgs::new(
|
||||
Some(Register::Rg8),
|
||||
Some(Register::Rg9),
|
||||
Some(Register::Rga),
|
||||
Some(15),
|
||||
);
|
||||
let instr = Instruction::And(args);
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
|
||||
let instr = Instruction::Xor(args);
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_decode_misc() {
|
||||
let instr = Instruction::Halt;
|
||||
let encoded = instr.encode();
|
||||
let decoded = Instruction::decode(encoded).unwrap();
|
||||
assert_eq!(instr, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_decode_invalid() {
|
||||
// Test with invalid opcode.
|
||||
let invalid_encoded = 0xF500_0000;
|
||||
let decode = Instruction::decode(invalid_encoded);
|
||||
|
||||
dbg!(&decode);
|
||||
|
||||
assert!(decode.is_err());
|
||||
}
|
||||
|
||||
// TODO: Get interrupts working.
|
||||
// #[test]
|
||||
// fn test_instruction_decode_interrupt() {
|
||||
// let interrupt = Interrupt::Software(10);
|
||||
// let instr = Instruction::Interrupt(interrupt);
|
||||
// let encoded = instr.encode();
|
||||
// let decoded = Instruction::decode(encoded).unwrap();
|
||||
// assert_eq!(instr, decoded);
|
||||
// }
|
||||
@@ -0,0 +1,24 @@
|
||||
#![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 build;
|
||||
pub mod instructions;
|
||||
pub mod logging;
|
||||
|
||||
pub mod prelude {
|
||||
//! A collection of types you should definitely import when working with this crate.
|
||||
pub use super::instructions::{
|
||||
Address, Instruction, InstructionType, Interrupt, Register, args::*, errors::*,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
use std::sync::{Arc, mpsc};
|
||||
|
||||
pub fn info(message: &str) {
|
||||
println!("\x1b[32mINFO:\x1b[0m {message}");
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LogReceiver {
|
||||
logs_rx: mpsc::Receiver<String>,
|
||||
sender: Logger,
|
||||
use_stdio: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Logger {
|
||||
use_stdio: bool,
|
||||
logs_tx: Arc<mpsc::Sender<String>>,
|
||||
}
|
||||
|
||||
impl Logger {
|
||||
#[must_use]
|
||||
pub fn new(logs_tx: mpsc::Sender<String>, use_stdio: bool) -> Self {
|
||||
Self {
|
||||
use_stdio: true,
|
||||
logs_tx: Arc::new(logs_tx),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn info(&self, message: &str) {
|
||||
let res = format!("\x1b[32mINFO:\x1b[0m {message}");
|
||||
if self.use_stdio {
|
||||
println!("{res}");
|
||||
}
|
||||
|
||||
self.logs_tx.send(res).expect("Failed to send log message");
|
||||
}
|
||||
}
|
||||
|
||||
impl LogReceiver {
|
||||
#[must_use]
|
||||
pub fn new(use_stdio: bool) -> Self {
|
||||
let (logs_tx, logs_rx) = mpsc::channel();
|
||||
Self {
|
||||
use_stdio,
|
||||
logs_rx,
|
||||
sender: Logger::new(logs_tx, use_stdio),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn logs(&self) -> Vec<String> {
|
||||
self.logs_rx.try_iter().collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn logger(&self) -> Logger {
|
||||
self.sender.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LogReceiver {
|
||||
fn default() -> Self {
|
||||
Self::new(true)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user