39 Commits

Author SHA1 Message Date
nullndvoid b91207bfde misc: update release profile for optimised builds 2025-06-29 04:33:24 +01:00
nullndvoid 4ac630ba02 misc: add 'profiling' profile. 2025-06-29 04:10:54 +01:00
nullndvoid 85e3d443cc assembler: small misc updates, I am tired 2025-06-29 03:52:53 +01:00
nullndvoid 0528768947 fmt: ran 'cargo fmt'. 2025-06-29 01:43:31 +01:00
nullndvoid 21582f1297 tokeniser/syntax: (db varname: -> db varname) dropped colon, updated tests. 2025-06-29 00:22:10 +01:00
nullndvoid 6ceb35d439 tokeniser: bugfixes to comma handling, regexes
TODO: Verify output is as expected, perhaps I can dump to file and compare token stream with known valid one?

Will add some extra tests of course!
2025-06-29 00:11:36 +01:00
nullndvoid 8bb252e941 tokeniser: return TokeniserErrors where relevant.
The UnexpectedEndOfInput case is a little vague.
2025-06-28 23:35:55 +01:00
nullndvoid 5317988fdd assembler: SourceInfo doc comment added to self.module. 2025-06-28 23:14:30 +01:00
nullndvoid d15e00c272 tokeniser: refactor to store Module directly in Tokeniser
We hereby avoid making extra copies of the PathBuf.

- Also updated tests to match the new API
2025-06-28 23:13:44 +01:00
nullndvoid a65dca6c5c tokeniser: errors now print with SourceInfo if added 2025-06-28 23:11:24 +01:00
nullndvoid b8be1bd95f tokeniser: add some actual tokeniser errors
TODO: Return these lol
2025-06-28 23:05:07 +01:00
nullndvoid f42c6d4095 assembler: refactor error handling and use ModuleId::new constructor 2025-06-28 23:03:13 +01:00
nullndvoid eebea82c4a assembler: start tokenising multiline strings (WIP) 2025-06-26 17:42:48 +01:00
nullndvoid ed4fcc8495 assembler: enhance error handling and tokenization logic 2025-06-26 17:00:14 +01:00
nullndvoid 40f8b1d57b assembler: fix clippy warnings 2025-06-25 19:49:20 +01:00
nullndvoid 68e459f32b assembler: use common to match registers 2025-06-25 19:29:56 +01:00
nullndvoid d9807b5b36 assembler: update tokeniser to allow extra prefixes and separators (0xDEAD_BEEF) 2025-06-25 19:15:51 +01:00
nullndvoid 7cb7525484 assembler: remove some current dead code 2025-06-25 17:56:45 +01:00
nullndvoid 7565374d5b assembler: Tokeniser updates, Compiler Engine is back finally 2025-06-25 17:55:34 +01:00
nullndvoid 9b9e153500 assembler: wrap Module's with Arc and update Tokeniser (still WIP)
Implements complete tokenizer with Arc-wrapped modules

Enhances module handling by wrapping Module instances in Arc for thread-safe sharing across the assembler pipeline.

Implements full tokenization logic with pattern matching for all token types including labels, registers, immediates, directives, instructions, symbols, and strings.

Adds comma token support and proper EOF handling to complete the lexical analysis phase.

Generated AI slop commit message, may not be super accurate or it may be a bit too serious lol.
2025-06-25 17:35:03 +01:00
nullndvoid 27267e3daa assembler: use smart pointer for modules since sourceinfo gets copy 2025-06-25 17:03:48 +01:00
nullndvoid fb84a6d3c3 assembler: clippy lints, better error formatting
Adds regex dependency and enhances error handling system

Introduces comprehensive error type hierarchy with specific variants for parser, symbol, codegen, threading, and IO errors to improve error reporting and debugging capabilities.

Adds regex crate for pattern matching in tokenizer implementation with pre-compiled patterns for labels, registers, immediates, directives, instructions, and symbols.

Enhances source info functionality with context printing and error underlining similar to compiler diagnostics.

Implements better error conversions and threading error handling for lock failures and panics.
2025-06-25 16:50:17 +01:00
nullndvoid 4e5db58a84 assembler: start refactoring/rewriting tokeniser 2025-06-25 14:48:45 +01:00
nullndvoid 11a57eab51 assembler: apply clippy lints 2025-06-25 14:33:48 +01:00
nullndvoid 20a7d42adb assembler: we failing DSA with this one 2025-06-25 14:31:53 +01:00
nullndvoid 9232f2ccab assembler: great leap forwards (more like the Cultural Revolution) 2025-06-25 03:26:50 +01:00
nullndvoid ce76820b6d assembler: begin wrangling 2025-06-25 02:25:46 +01:00
nullndvoid f72f36cd47 assembler: save currently broken refactors, its simpler to wipe then rebuild the assembler 2025-06-25 02:19:00 +01:00
nullndvoid 11ba09ab43 assembler: broke everything, currently modularising 2025-06-24 23:19:20 +01:00
nullndvoid 65efa8d423 misc: fix some clippy errors 2025-06-24 22:15:51 +01:00
nullndvoid ebae99811b misc: get rid of some errors from Cargo lol 2025-06-24 22:10:55 +01:00
zxq5 77331f65ab idk, i refactored some stuff ig 2025-06-24 22:10:55 +01:00
zxq5 6f2bb477ac finished the interpreter 2025-06-24 22:09:55 +01:00
zxq5 d87bf6bbb0 progress on debugging bf.dsa 2025-06-24 22:09:55 +01:00
zxq5 449612ac19 added step(n) feature to emulator, allowing for stepping n instructions at a time 2025-06-24 22:09:55 +01:00
zxq5 987c2b4b9a updated dependencies 2025-06-24 22:09:55 +01:00
zxq5 a55dfe616e finished refactor of emulator - started on loader (needs significant changes before functional in the way that I would like) 2025-06-24 22:09:55 +01:00
zxq5 2c44f48232 added error handling to emulator 2025-06-24 22:09:55 +01:00
nullndvoid 00a28e7711 elf: will start using clap to parse assembler arguments for CLI
I am tired af for some reason
2025-06-23 21:30:48 +01:00
107 changed files with 11948 additions and 11278 deletions
+4
View File
@@ -5,3 +5,7 @@ rustc-wrapper = "sccache"
[future-incompat-report]
frequency = "always"
[profile.profiling]
inherits = "release"
debug = true
-1
View File
@@ -1,3 +1,2 @@
/target
**/*.env
Cargo.lock
+1 -3
View File
@@ -5,7 +5,5 @@
"files.eol": "\n",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true,
"gitea.owner": "LowLevelDevs",
"gitea.repo": "damn_simple_architecture",
"files.trimTrailingWhitespace": true
}
Generated
+4445
View File
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -1,7 +1,7 @@
cargo-features = ["codegen-backend"]
[workspace]
members = ["emulator", "common", "assembler", "dsa_editor", "compiler", "c_compiler"]
members = ["emulator", "common", "assembler", "dsa_editor"]
resolver = "3"
[workspace.package]
@@ -15,3 +15,7 @@ panic = "abort" # Cranelift does not support stack unwinds.
lto = false
debug = true
incremental = false # sccache does not support caching incremental crates.
[profile.release]
incremental = true
lto = "fat"
+3
View File
@@ -13,6 +13,9 @@ name = "assembler"
path = "src/lib.rs"
[dependencies]
clap = { version = "4.5.40", features = ["derive"] }
common = { path = "../common" }
num_cpus = "1.17.0"
regex = "1.11.1"
threadpool = "1.8.1"
uuid = { version = "1.17.0", features = ["v4"] }
+21
View File
@@ -0,0 +1,21 @@
use clap::{Parser, ValueEnum};
#[derive(Debug, Parser, Default)]
pub struct Args {
/// The output format to assemble to. Currently just ELF or a flat binary.
#[arg(value_enum)]
output_format: Option<OutputFormat>,
/// Whether the relocatable object files should be statically linked into a single
/// executable or library.
link: bool,
}
#[derive(Debug, Clone, Copy, ValueEnum, Default)]
/// The executable format the output should take.
pub enum OutputFormat {
/// An ELF file.
#[default]
Elf,
/// A flat binary file.
Flat,
}
-264
View File
@@ -1,264 +0,0 @@
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>,
}
-347
View File
@@ -1,347 +0,0 @@
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(reg_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(amount_token) = args.get(1) else {
return Err(AssembleError::MissingArgument(1));
};
let reg = expect_token!(reg_token, Register)?;
let amount = expect_token!(amount_token, Immediate)? as u8;
match opcode {
Opcode::Shl => Ok(Instruction::ShiftLeft(args!(R, sr1: reg, shamt: amount))),
Opcode::Shr => Ok(Instruction::ShiftRight(args!(R, sr1: reg, shamt: amount))),
_ => 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))
}
-368
View File
@@ -1,368 +0,0 @@
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)
}
-173
View File
@@ -1,173 +0,0 @@
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),
})))
}
}
-138
View File
@@ -1,138 +0,0 @@
//! Macros used throughout the assembler
use crate::assembler::model::{Node, Opcode, Symbol, Token};
/// Parse DSA assembly code with optional formatting
///
/// # Examples
/// ```
/// // 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 };
}
-263
View File
@@ -1,263 +0,0 @@
#![allow(dead_code, unused)]
use std::{
collections::HashSet,
fmt, fs,
hash::{DefaultHasher, Hash, Hasher},
path::{Path, PathBuf},
sync::{Arc, Mutex, mpsc},
thread,
};
pub use common::logging::log;
use common::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},
};
use crate::util::logging::{Entry, Logger};
pub struct CompilerEngine {
result_tx: mpsc::Sender<Result<Vec<Instruction>, AssembleError>>,
result_rx: Option<mpsc::Receiver<Result<Vec<Instruction>, AssembleError>>>,
is_running: bool,
}
impl CompilerEngine {
#[must_use]
pub fn new() -> Self {
let (tx, rx) = mpsc::channel();
Self {
result_tx: tx,
result_rx: Some(rx),
is_running: false,
}
}
/// Start the compilation process in a separate thread
pub fn start_compilation(&mut self, src: &Path) {
if self.is_running {
return;
}
let src = src.to_path_buf();
let tx = self.result_tx.clone();
thread::spawn(move || {
let result = assemble(&src);
tx.send(result)
.expect("Failed to send compilation result from worker thread");
});
self.is_running = true;
}
/// Check if compilation is complete and get the result
pub fn try_get_result(&mut self) -> Option<Result<Vec<Instruction>, AssembleError>> {
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)
}
Err(mpsc::TryRecvError::Empty) => None,
Err(mpsc::TryRecvError::Disconnected) => {
self.is_running = false;
Some(Err(AssembleError::Generic))
}
}
}
/// Block until compilation is complete and return the result
pub fn wait_for_result(&mut self) -> Result<Vec<Instruction>, AssembleError> {
if !self.is_running {
return Err(AssembleError::Generic);
}
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
} else {
self.is_running = false;
Err(AssembleError::Generic)
}
}
}
fn assemble(src: &Path) -> 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)?;
let mut nodes = program.nodes.clone();
create_sections(&mut nodes)?;
resolve_symbols(&mut nodes)?;
log("Generating assembly output...");
let instructions = codegen(nodes)?;
log("Compilation Successful");
Ok(instructions)
}
impl Default for CompilerEngine {
fn default() -> Self {
Self::new()
}
}
fn prepare_dependency(
path: &Path,
modules: &mut HashSet<u64>,
program: &mut Program,
) -> 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() {
log(&format!(
"{:20} {:20} [{}]",
"Building",
filename,
path.display()
));
}
let src = fs::read_to_string(path)
.map_err(|_| AssembleError::InvalidFile(path.to_path_buf()))?;
let file_hash = quick_hash(path);
log(&format!("{:20} {:20}", "Tokenising", filename));
let tokens = lexer::lexer(src, file_hash)?;
log(&format!("{:20} {:20}", "Parsing", filename));
let parsed = Parser::parse_nodes(tokens)?;
log(&format!("{:20} {:20}", "Resolving Deps", filename));
// Get the parent directory of the source file to use as the base directory
let base_dir = path
.parent()
.ok_or_else(|| AssembleError::InvalidFile(path.to_path_buf()))?;
let mut nodes = expand_pseudo_ops(parsed, file_hash)?;
nodes = resolve_dependencies(nodes, base_dir)?;
let deps = Parser::get_dependencies(&nodes, path)?;
log(&format!("{:20} {:20}", "Expanding Pseudo-ops", filename));
// add a section instruction
nodes.insert(
0,
node!(None, Opcode::Segment, Token::Immediate(file_hash as u32)),
);
// for n in &nodes {
// println!("{n}");
// }
program.add_module(nodes);
for dep in deps {
log(&format!(
"{:20} {:20}",
"Including",
dep.file_name()
.and_then(|f| f.to_str())
.expect("Dependency path has no file name or is not valid UTF-8")
));
let dep_hash = quick_hash(&dep);
if modules.insert(dep_hash) {
prepare_dependency(dep.as_path(), modules, program)?;
}
}
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()
}
-368
View File
@@ -1,368 +0,0 @@
use std::path::{Path, PathBuf};
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>;
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 mut offset = Token::Immediate(0);
if let Ok(next) = self.peek_next()
&& expect_type!(next, Immediate).is_ok() {
offset = self.next()?;
}
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 mut offset = Token::Immediate(0);
if let Ok(next) = self.peek_next()
&& expect_type!(next, Immediate).is_ok() {
offset = self.next()?;
}
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 reg1 = expect_type!(self.next()?, Register, Symbol)?;
let reg2 = expect_type!(self.next()?, Register, Symbol)?;
args = vec![reg1, reg2];
}
Opcode::Shl | Opcode::Shr => {
let reg = expect_type!(self.next()?, Register, Symbol)?;
let num = expect_type!(self.next()?, Immediate)?;
args = vec![reg, num];
}
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())
}
}
}
-156
View File
@@ -1,156 +0,0 @@
use std::{
collections::HashMap,
fs::canonicalize,
path::{Path, PathBuf},
};
use common::prelude::Register;
use crate::assembler::quick_hash;
use crate::assembler::{
log,
model::{Module, Node, Opcode, Symbol, Token},
};
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(())
}
+374
View File
@@ -0,0 +1,374 @@
//! Simple compiler engine that orchestrates the entire compilation process.
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::path::Path;
use std::sync::mpsc;
use std::thread;
use crate::error::{AssembleErrorKind, IoErrorKind};
use crate::{
context::AssemblerContext,
error::AssembleError,
model::module::ModuleId,
source::{token::Token, tokeniser::Tokeniser},
};
use common::instructions::Instruction;
/// Error type for the `CompilerEngine`
#[derive(Debug)]
pub enum EngineError {
/// Assembly error during compilation
Assembly(AssembleError),
/// Channel communication error
Channel(String),
/// Other generic error
Other(String),
}
impl fmt::Display for EngineError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Assembly(e) => write!(f, "Assembly error: {e}"),
Self::Channel(msg) => write!(f, "Channel error: {msg}"),
Self::Other(msg) => write!(f, "Engine error: {msg}"),
}
}
}
impl std::error::Error for EngineError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Assembly(e) => Some(e),
Self::Channel(_) | Self::Other(_) => None,
}
}
}
// Convert from AssembleError
impl From<AssembleError> for EngineError {
fn from(error: AssembleError) -> Self {
Self::Assembly(error)
}
}
// Convert from mpsc::SendError
impl<T> From<mpsc::SendError<T>> for EngineError {
fn from(error: mpsc::SendError<T>) -> Self {
Self::Channel(format!("Send error: {error}"))
}
}
// Convert from mpsc::RecvError
impl From<mpsc::RecvError> for EngineError {
fn from(error: mpsc::RecvError) -> Self {
Self::Channel(format!("Receive error: {error}"))
}
}
// Convert from mpsc::TryRecvError
impl From<mpsc::TryRecvError> for EngineError {
fn from(error: mpsc::TryRecvError) -> Self {
Self::Channel(format!("Try receive error: {error}"))
}
}
// Convert from String for generic errors
impl From<String> for EngineError {
fn from(error: String) -> Self {
Self::Other(error)
}
}
// Convert from &str for convenience
impl From<&str> for EngineError {
fn from(error: &str) -> Self {
Self::Other(error.to_string())
}
}
/// Simple compiler engine that orchestrates the entire compilation process.
pub struct CompilerEngine {
result_tx: mpsc::Sender<Result<Vec<Instruction>, EngineError>>,
result_rx: Option<mpsc::Receiver<Result<Vec<Instruction>, EngineError>>>,
is_running: bool,
}
impl CompilerEngine {
/// Create a new compiler engine
#[must_use]
pub fn new() -> Self {
let (tx, rx) = mpsc::channel();
Self {
result_tx: tx,
result_rx: Some(rx),
is_running: false,
}
}
/// Start the compilation process in a separate thread
pub fn start_compilation<P: AsRef<Path>>(&mut self, src: P) {
if self.is_running {
return;
}
let src = src.as_ref().to_path_buf();
let tx = self.result_tx.clone();
thread::spawn(move || {
let result = assemble(&src).map_err(EngineError::from);
let _ = tx.send(result); // Ignore send errors if receiver is dropped
});
self.is_running = true;
}
/// Check if compilation is complete and get the result
pub fn try_get_result(&mut self) -> Option<Result<Vec<Instruction>, EngineError>> {
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)
}
Err(mpsc::TryRecvError::Empty) => None,
Err(mpsc::TryRecvError::Disconnected) => {
self.is_running = false;
Some(Err(EngineError::Channel(
"Compilation thread disconnected".to_string(),
)))
}
}
}
/// Block until compilation is complete and return the result
pub fn wait_for_result(&mut self) -> Result<Vec<Instruction>, EngineError> {
if !self.is_running {
return Err(EngineError::Other("No compilation in progress".to_string()));
}
let result = self
.result_rx
.take()
.expect("result_rx should be Some while waiting for compilation result")
.recv()
.map_err(EngineError::from)?;
self.is_running = false;
result
}
/// Add a source file to be compiled (for compatibility with old interface)
pub fn add_source_file<P: AsRef<Path>>(
&mut self,
path: P,
) -> Result<(), EngineError> {
let path = path.as_ref().to_path_buf();
// Verify file exists
if !path.exists() {
return Err(EngineError::Assembly(AssembleError::new_other_error(
AssembleErrorKind::Io(crate::error::IoError::new(
IoErrorKind::NotFound,
Some(format!("Source file not found: {}", path.display())),
)),
)));
}
// For now, just validate the file exists
// TODO: Could store multiple files for batch compilation
Ok(())
}
/// Compile all added source files (synchronous version)
pub fn compile(&mut self) -> Result<CompileResult, EngineError> {
// This is a placeholder that matches the old interface
// For now, return empty result since we don't have a specific file to compile
Ok(CompileResult {
modules: Vec::new(),
tokens: HashMap::new(),
})
}
/// Get access to the assembler context (placeholder)
pub fn context(&self) -> Result<&AssemblerContext, EngineError> {
// For now, return an error since we're using the threaded approach
// TODO: Integrate context properly when we have more compilation phases
Err(EngineError::Other(
"Context not available in threaded mode".to_string(),
))
}
}
impl Default for CompilerEngine {
fn default() -> Self {
Self::new()
}
}
/// Main assembly function that orchestrates the compilation process
fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
// Verify the file exists
if !src.exists() {
return Err(AssembleError::new_other_error(AssembleErrorKind::Io(
crate::error::IoError::new(
IoErrorKind::NotFound,
Some(format!("Source file not found: {}", src.display())),
),
)));
}
let mut modules = HashSet::new();
let mut all_tokens = HashMap::new();
let mut module_ids = Vec::new();
// Create a new assembler context for this compilation
let context = AssemblerContext::new();
// Process the main file and its dependencies
prepare_dependency(
src,
&mut modules,
&mut all_tokens,
&mut module_ids,
&context,
)?;
// Phase 2: Parse tokens into AST (placeholder for now)
// TODO: Add parser here when implemented
println!("Phase 2: Parsing {} modules...", module_ids.len());
// Phase 3: Symbol resolution (placeholder for now)
// TODO: Add symbol resolution here when implemented
println!("Phase 3: Resolving symbols...");
// Phase 4: Code generation (placeholder for now)
// TODO: Add code generation here when implemented
println!("Phase 4: Generating code...");
// For now, return empty instructions since we don't have the full pipeline yet
Ok(Vec::new())
}
/// Prepare a dependency (file) for compilation
fn prepare_dependency(
path: &Path,
modules: &mut HashSet<u64>,
all_tokens: &mut HashMap<ModuleId, Vec<Token>>,
module_ids: &mut Vec<ModuleId>,
context: &AssemblerContext,
) -> Result<(), AssembleError> {
let filename = path.file_name().and_then(|n| n.to_str()).ok_or_else(|| {
AssembleError::new_other_error(AssembleErrorKind::Io(crate::error::IoError::new(
IoErrorKind::InvalidData,
Some("Failed to get file name from path".to_string()),
)))
})?;
// Calculate a simple hash for the file (similar to quick_hash)
let file_hash = calculate_file_hash(path);
// Skip if we've already processed this module
if modules.contains(&file_hash) {
return Ok(());
}
modules.insert(file_hash);
if let Ok(canonical_path) = path.canonicalize() {
println!("Building {} [{}]", filename, canonical_path.display());
}
// Phase 1: Tokenize the file
println!("Tokenising {filename}");
let tokeniser = Tokeniser::new(path, context)?;
let tokens = tokeniser.tokenise()?;
// Get the module ID that was registered during tokenization
let module_id = get_module_id_for_file(path, context)?;
all_tokens.insert(module_id, tokens);
module_ids.push(module_id);
// TODO: Parse tokens to find dependencies (.include directives, etc.)
// For now, we'll just process the single file
println!("Resolving dependencies for {filename}");
Ok(())
}
/// Calculate a simple hash for a file path (similar to the old `quick_hash`)
fn calculate_file_hash(path: &Path) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
if let Ok(canonical) = path.canonicalize() {
canonical.hash(&mut hasher);
} else {
path.hash(&mut hasher);
}
hasher.finish()
}
/// Get the module ID for a given source file
fn get_module_id_for_file(
file_path: &Path,
context: &AssemblerContext,
) -> Result<ModuleId, AssembleError> {
{
let registry = context.module_registry.read()?;
// Find module by path.
for module in registry.modules() {
if module.path == file_path {
return Ok(module.id);
}
}
}
Err(AssembleError::new_other_error(AssembleErrorKind::Io(
crate::error::IoError::new(
IoErrorKind::NotFound,
Some(format!(
"Module not found for file: {}",
file_path.display()
)),
),
)))
}
/// Result of compilation. This is useless at present but compiles.
#[derive(Debug)]
pub struct CompileResult {
pub modules: Vec<ModuleId>,
pub tokens: HashMap<ModuleId, Vec<Token>>,
}
impl CompileResult {
/// Get tokens for a specific module
#[must_use]
pub fn get_tokens(&self, module_id: &ModuleId) -> Option<&Vec<Token>> {
self.tokens.get(module_id)
}
/// Get all module IDs
#[must_use]
pub fn module_ids(&self) -> &[ModuleId] {
&self.modules
}
/// Get total number of tokens across all modules
#[must_use]
pub fn total_tokens(&self) -> usize {
self.tokens.values().map(std::vec::Vec::len).sum()
}
}
+28
View File
@@ -0,0 +1,28 @@
//! This module contains the global asembler context to be passed to functions that need
//! it.
use std::sync::RwLock;
use crate::{model::module_registry::ModuleRegistry, symtab::SymbolTable};
/// Global state to be passed around.
pub struct AssemblerContext {
pub symbol_table: RwLock<SymbolTable>,
pub module_registry: RwLock<ModuleRegistry>,
}
impl Default for AssemblerContext {
fn default() -> Self {
Self::new()
}
}
impl AssemblerContext {
#[must_use]
pub fn new() -> Self {
Self {
symbol_table: RwLock::new(SymbolTable::new()),
module_registry: RwLock::new(ModuleRegistry::new()),
}
}
}
+275
View File
@@ -0,0 +1,275 @@
//! This module contains code for various types of errors that may occur when assembling a
//! set of source DSA files.
use std::fmt::{Debug, Display};
use crate::source::{source_info::SourceInfo, tokeniser::error::TokeniserError};
/// An error that may occur during the assembly of a set of source files.
#[derive(Debug)]
pub struct AssembleError {
/// Display implementation can handle when the source code information is shown or
/// not.
source_info: Option<SourceInfo>,
/// The type of assembly error that occurred.
kind: AssembleErrorKind,
/// Whether context should be added to errors being printed. This might get changed
/// to Verbosity in the future.
display_quietly: bool,
}
impl AssembleError {
#[must_use]
pub const fn new_source_error(
source_info: SourceInfo,
kind: AssembleErrorKind,
) -> Self {
Self {
source_info: Some(source_info),
kind,
display_quietly: false,
}
}
#[must_use]
pub const fn new_other_error(kind: AssembleErrorKind) -> Self {
Self {
source_info: None,
kind,
display_quietly: true,
}
}
/// Prints a parser error to the screen.
fn print_parser_error(
&self,
f: &mut std::fmt::Formatter<'_>,
parse_error: &ParserError,
) -> std::fmt::Result {
let Some(source_info) = &self.source_info else {
write!(
f,
"parser error thrown with no source information. Error: {parse_error}"
)?;
return Ok(());
};
writeln!(f, "parser error of type `{parse_error}`.\n")?;
// Prints out the context for our error.
if !self.display_quietly {
source_info.print_context_with_underline().map_err(|e| {
_ = writeln!(f, "print context error: {e}");
std::fmt::Error {}
})?;
}
Ok(())
}
/// Prints a tokeniser error to the screen.
fn print_tokeniser_error(
&self,
f: &mut std::fmt::Formatter<'_>,
err: &TokeniserError,
) -> std::fmt::Result {
let Some(source_info) = &self.source_info else {
write!(
f,
"Tokeniser error thrown with no source information. Error: {err}"
)?;
return Ok(());
};
writeln!(f, "tokeniser error of type `{err}`.\n")?;
// Prints out the context for our error.
source_info.print_context_with_underline().map_err(|e| {
_ = writeln!(f, "Print context error: {e}");
std::fmt::Error {}
})?;
Ok(())
}
}
impl Display for AssembleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(info) = &self.source_info {
write!(f, "At {info}, got ")?;
match &self.kind {
AssembleErrorKind::Parser(err) => self.print_parser_error(f, err)?,
AssembleErrorKind::Tokeniser(err) => {
self.print_tokeniser_error(f, err)?;
}
_ => write!(f, "{}", self.kind)?,
}
writeln!(f)?;
return Ok(());
}
// Handle errors without SourceInfo.
write!(f, "{}", self.kind)?;
Ok(())
}
}
/// Marker trait.
impl std::error::Error for AssembleError {}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum AssembleErrorKind {
/// Usually unexpected I/O errors. Not normally recoverable.
Io(IoError),
/// Errors emitted from the [`Tokeniser`].
Tokeniser(TokeniserError),
Parser(ParserError),
Symbol(SymbolError),
Codegen(CodegenError),
Threading(ThreadingError),
/// Returned for code where the functionality has not yet been implemented but we
/// don't want the program to panic.
Unimplemented(&'static str),
}
#[derive(Debug, Clone)]
pub enum ParserError {
UnexpectedToken,
MissingOperand,
InvalidInstruction,
MissingLabel,
DuplicateLabel,
}
impl Display for ParserError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnexpectedToken => write!(f, "unexpected token"),
Self::MissingOperand => write!(f, "missing operand"),
Self::InvalidInstruction => write!(f, "invalid instruction"),
Self::MissingLabel => write!(f, "missing label"),
Self::DuplicateLabel => write!(f, "duplicate label"),
}
}
}
#[derive(Debug, Clone)]
pub enum SymbolError {
Undefined,
Duplicate,
CircularDependency,
InvalidReference,
}
impl Display for SymbolError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Undefined => write!(f, "undefined symbol"),
Self::Duplicate => write!(f, "duplicate symbol"),
Self::CircularDependency => write!(f, "circular dependency"),
Self::InvalidReference => write!(f, "invalid reference"),
}
}
}
#[derive(Debug, Clone)]
pub enum CodegenError {
InvalidOperand,
OutOfRange,
UnsupportedInstruction,
}
impl Display for CodegenError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidOperand => write!(f, "invalid operand"),
Self::OutOfRange => write!(f, "out of range"),
Self::UnsupportedInstruction => write!(f, "unsupported instruction"),
}
}
}
#[derive(Debug, Clone)]
pub enum ThreadingError {
LockFailed,
ThreadPanic,
}
impl Display for ThreadingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::LockFailed => write!(f, "lock failed"),
Self::ThreadPanic => write!(f, "thread panic"),
}
}
}
#[derive(Debug, Clone)]
pub struct IoError {
msg: Option<String>,
kind: IoErrorKind,
}
impl IoError {
#[must_use]
pub const fn new(kind: IoErrorKind, msg: Option<String>) -> Self {
Self { msg, kind }
}
}
#[derive(Debug, Clone)]
pub enum IoErrorKind {
NotFound,
PermissionDenied,
InvalidData,
Other,
}
impl std::fmt::Display for IoErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotFound => write!(f, "file not found"),
Self::PermissionDenied => write!(f, "permission denied"),
Self::InvalidData => write!(f, "invalid data"),
Self::Other => write!(f, "other I/O error"),
}
}
}
impl std::fmt::Display for IoError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.kind)?;
if let Some(msg) = &self.msg {
write!(f, ", \"{msg}\"")?;
}
Ok(())
}
}
impl Display for AssembleErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Tokeniser(why) => write!(f, "tokeniser error: {why}"),
Self::Unimplemented(why) => write!(f, "used unimplemented feature: {why}"),
Self::Io(why) => write!(f, "problem occurred with I/O: {why}"),
#[allow(unreachable_patterns)]
_ => write!(
f,
"unhandled error type in Display implementation! See error.rs!"
),
}
}
}
pub mod conversions;
+67
View File
@@ -0,0 +1,67 @@
use std::{
io::ErrorKind,
sync::{PoisonError, RwLockReadGuard, RwLockWriteGuard},
};
use crate::error::{AssembleError, IoError, IoErrorKind};
use super::{AssembleErrorKind, ThreadingError};
impl From<std::io::Error> for IoError {
fn from(err: std::io::Error) -> Self {
let kind = match err.kind() {
ErrorKind::NotFound => IoErrorKind::NotFound,
ErrorKind::PermissionDenied => IoErrorKind::PermissionDenied,
ErrorKind::InvalidData => IoErrorKind::InvalidData,
_ => IoErrorKind::Other,
};
let msg = err.to_string();
Self::new(kind, Some(msg))
}
}
impl From<std::io::Error> for AssembleError {
fn from(err: std::io::Error) -> Self {
Self::new_other_error(AssembleErrorKind::Io(err.into()))
}
}
// TODO: Maybe attempt recovery? To be honest we don't want any threads to panic at all,
// or we want them all to panic spectacularly.
impl<T> From<PoisonError<RwLockReadGuard<'_, T>>> for AssembleError {
fn from(err: PoisonError<RwLockReadGuard<'_, T>>) -> Self {
Self::new_other_error(AssembleErrorKind::Threading(err.into()))
}
}
impl<T> From<PoisonError<RwLockReadGuard<'_, T>>> for ThreadingError {
fn from(_err: PoisonError<RwLockReadGuard<'_, T>>) -> Self {
Self::LockFailed
}
}
impl<T> From<PoisonError<RwLockWriteGuard<'_, T>>> for AssembleError {
fn from(err: PoisonError<RwLockWriteGuard<'_, T>>) -> Self {
Self::new_other_error(AssembleErrorKind::Threading(err.into()))
}
}
impl<T> From<PoisonError<RwLockWriteGuard<'_, T>>> for ThreadingError {
fn from(_err: PoisonError<RwLockWriteGuard<'_, T>>) -> Self {
Self::LockFailed
}
}
impl From<std::fmt::Error> for AssembleError {
fn from(err: std::fmt::Error) -> Self {
IoError::new(IoErrorKind::Other, Some(err.to_string())).into()
}
}
impl From<IoError> for AssembleError {
fn from(err: IoError) -> Self {
Self::new_other_error(AssembleErrorKind::Io(err))
}
}
+10 -9
View File
@@ -12,17 +12,18 @@
clippy::match_wildcard_for_single_variants
)]
pub mod assembler;
pub mod image_builder;
pub mod tooling;
pub mod args;
// pub mod tooling;
pub mod compiler_engine;
pub mod context;
pub mod error;
pub mod model;
pub mod source;
pub mod symtab;
mod util;
pub mod prelude {
pub use crate::assembler::CompilerEngine;
pub use crate::image_builder;
pub use crate::tooling::brainf;
pub use crate::tooling::project;
}
// pub mod prelude {}
use num_cpus as _;
use threadpool as _;
+75 -47
View File
@@ -1,64 +1,92 @@
use std::sync::Arc;
use assembler::{
error::{AssembleError, AssembleErrorKind, ParserError},
model::module::Module,
source::{source_info::SourceInfo, token::TokenType, tokeniser::Tokeniser},
};
use common as _;
use num_cpus as _;
use threadpool as _;
use assembler::{
prelude::*,
tooling::{brainf, project},
};
use std::{fs, io::Write, path::PathBuf};
// use clap::Parser;
// use std::{fs, io::Write, path::PathBuf};
fn main() {
// Parse command line arguments
let args: Vec<String> = std::env::args().collect();
fn main() -> Result<(), AssembleError> {
// // Parse command line arguments
// let args: Vec<String> = std::env::args().collect();
let contents = include_bytes!("../../resources/dsa/bf.dsa").to_vec();
if args.len() == 2 && args[1] == "init" {
project::tool_libcreate();
std::process::exit(0);
let module = Arc::new(Module::new("resources/dsa/bf.dsa")?);
let tok = Tokeniser::from_data(contents, module.clone());
let ts = tok
.tokenise()?
.into_iter()
.filter(|t| !matches!(t.token_type, TokenType::Eof | TokenType::Newline));
for t in ts {
t.source_info.print_context_with_underline()?;
}
if args.len() == 2 && args[1] == "brainf" {
let src = PathBuf::from("brainf.bf");
let result = brainf::build(&src);
let test_error: AssembleError = AssembleError::new_source_error(
SourceInfo::new(45, module.clone(), 4..7),
AssembleErrorKind::Parser(ParserError::InvalidInstruction),
);
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,
};
eprintln!("\n\n{test_error}");
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);
}
}
Ok(())
std::process::exit(0);
}
// let _clap_args = assembler::args::Args::parse();
if args.len() != 5 || args[1] != "-i" || args[3] != "-o" {
eprintln!("Usage: {} -i input_path -o output_path", args[0]);
std::process::exit(1);
}
// if args.len() == 2 && args[1] == "init" {
// // project::tool_libcreate();
// std::process::exit(0);
// }
let input_path = &args[2];
let output_path = &args[4];
let src = PathBuf::from(input_path);
// if args.len() == 2 && args[1] == "brainf" {
// let src = PathBuf::from("brainf.bf");
// // let result = brainf::build(&src);
// Initialize the compiler engine
let mut compiler = CompilerEngine::new();
compiler.start_compilation(&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,
// };
// Or block until done
let result = compiler.wait_for_result().unwrap();
// // 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);
// // }
// // }
for instruction in result {
if let Err(e) = fs::write(output_path, 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 src = PathBuf::from(input_path);
// // Initialize the compiler engine
// let mut compiler = CompilerEngine::new();
// compiler.start_compilation(&src);
// // Or block until done
// let result = compiler.wait_for_result().unwrap();
// for instruction in result {
// if let Err(e) = fs::write(output_path, instruction.encode().to_be_bytes()) {
// eprintln!("Failed to write to output file: {e}");
// std::process::exit(1);
// }
// }
}
+5
View File
@@ -0,0 +1,5 @@
//! This module contains the underlying data models and enums used by the Assembler.
pub mod module;
pub mod module_registry;
pub mod symbol;
+110
View File
@@ -0,0 +1,110 @@
//! This module contains the [`Module`] type and associated types. Each compilation unit
//! (file) is represented by a module which is used to namespace "function" calls and
//! accesses to global variables.
//!
//! They have unique identifiers in the form of UUIDs.
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use regex::Regex;
use uuid::Uuid;
use crate::{
error::{AssembleError, AssembleErrorKind, IoError, IoErrorKind},
model::module_registry::ModuleRegistry,
};
/// The ID for a module. A tuple struct for type safety.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
pub struct ModuleId(Uuid);
impl ModuleId {
#[must_use]
pub fn new() -> Self {
Self(Uuid::new_v4())
}
#[must_use]
pub const fn from_module(module: &Module) -> Self {
module.id
}
/// Convenience method to get the [`Module`] from a [`ModuleId`].
#[must_use]
pub fn to_module<'m>(&self, registry: &'m ModuleRegistry) -> Option<&'m Arc<Module>> {
registry.get(self)
}
/// Convenience method to get the [`Module`] name from a [`ModuleId`].
#[must_use]
pub fn to_module_name(self, registry: &ModuleRegistry) -> Option<&str> {
self.to_module(registry).map(|module| module.name.as_str())
}
}
impl Default for ModuleId {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for ModuleId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
/// A single source file or compilation unit. Stores its own symbol table.
#[derive(Debug, Clone)]
pub struct Module {
/// The name of the module. This is typically the name of the file, less the `.dsa`
/// extension.
pub name: String,
/// The file path to the module. This is an absolute path.
pub path: PathBuf,
/// A unique ID for this module.
pub id: ModuleId,
}
impl std::hash::Hash for Module {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.0.hash(state);
}
}
impl Module {
pub fn new<P: AsRef<Path>>(p: P) -> Result<Self, AssembleError> {
let path = p.as_ref().to_path_buf();
let name = Self::extract_module_name(&path)?;
let id = ModuleId::new();
Ok(Self { name, path, id })
}
/// Gets the name for a module from the path.
fn extract_module_name<P: AsRef<Path>>(path: P) -> Result<String, AssembleError> {
let extensions_regex = Regex::new(".(dsa|S|asm)$")
.expect("For some reason the regular expression failed to compile!");
let module_name = path
.as_ref()
.file_name()
.map(|f| f.to_string_lossy())
.ok_or_else(|| {
AssembleError::new_other_error(AssembleErrorKind::Io(IoError::new(
IoErrorKind::InvalidData,
Some(
"the filename couldn't be extracted, is it valid UTF-8?"
.to_string(),
),
)))
})?;
// Strip any file extensions given. We don't care for now.
let out = extensions_regex.replace(&module_name, "");
Ok(out.to_string())
}
}
+44
View File
@@ -0,0 +1,44 @@
//! This module contains the code for the module registry. This is a singleton storing all
//! the modules being assembled.
use std::{collections::HashMap, sync::Arc};
use super::module::{Module, ModuleId};
/// Stores all the [`Module`]'s to be assembled.
pub struct ModuleRegistry {
modules: HashMap<ModuleId, Arc<Module>>,
}
impl Default for ModuleRegistry {
fn default() -> Self {
Self::new()
}
}
impl ModuleRegistry {
#[must_use]
pub fn new() -> Self {
Self {
modules: HashMap::new(),
}
}
/// Gets a [`Module`] by ID.
#[must_use]
pub fn get(&self, module_id: &ModuleId) -> Option<&Arc<Module>> {
self.modules.get(module_id)
}
/// Adds a [`Module`] and returns its [`ModuleId`].
pub fn add(&mut self, module: Arc<Module>) -> ModuleId {
let id = module.id;
self.modules.insert(id, module);
id
}
/// Returns an iterator of modules.
pub fn modules(&self) -> impl Iterator<Item = &Arc<Module>> {
self.modules.values()
}
}
+165
View File
@@ -0,0 +1,165 @@
//! This module contains the definitions for a Symbol.
use std::collections::HashSet;
use uuid::Uuid;
use crate::{model::module::ModuleId, symtab::SymbolTable};
/// Tuple struct for type safety. Has methods for fetching symbols by ID.
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub struct SymbolId(Uuid);
impl From<Symbol> for SymbolId {
fn from(sym: Symbol) -> Self {
sym.id
}
}
impl Default for SymbolId {
fn default() -> Self {
Self::new()
}
}
impl SymbolId {
#[must_use]
pub fn new() -> Self {
Self(Uuid::new_v4())
}
/// Convenience method to get the [`Module`] from a [`ModuleId`].
#[must_use]
pub fn to_module<'s>(&self, registry: &'s SymbolTable) -> Option<&'s Symbol> {
registry.get(self)
}
/// Convenience method to get the [`Module`] name from a [`ModuleId`].
#[must_use]
pub fn to_module_name(self, registry: &SymbolTable) -> Option<&str> {
self.to_module(registry).map(|module| module.name.as_str())
}
}
/// A symbol is a named reference that may be resolved later to an address by a linker.
#[derive(Debug)]
pub struct Symbol {
/// Stored cheaply instead of the name. Shall be stored in the symbol table under
/// this key.
pub id: SymbolId,
/// The human-readable name for the symbol.
pub name: String,
pub visibility: Visibility,
pub symbol_type: SymbolType,
/// The id of the module the symbol is defined in. This will be different for symbols
/// in different objects.
pub module_id: ModuleId,
/// Whether or not the symbol requires relocating.
pub needs_relocation: bool,
/// A list of the symbol's dependencies.
///
/// e.g.
///
/// ```dsa
/// main:
/// call another_func
///
/// another_func:
/// // Code goes here
/// ret
/// ```
///
/// Where `main` depends on `another_func`.
pub dependencies: HashSet<SymbolId>,
/// The address of the symbol.
pub address: Option<u32>,
/// The section the symbol is in.
/// TODO: Perhaps make this a proper type?
pub section: Option<String>,
pub size: Option<u32>,
}
impl Symbol {
#[must_use]
pub fn new(
name: String,
module_id: ModuleId,
visibility: Visibility,
symbol_type: SymbolType,
) -> Self {
Self {
id: SymbolId::new(),
name,
module_id,
address: None,
section: None,
size: None,
visibility,
symbol_type,
needs_relocation: false,
dependencies: HashSet::new(),
}
}
/// Adds a dependency on another [`Symbol`].
pub fn add_dependency(&mut self, dep: SymbolId) {
if self.id == dep {
return;
}
// We can resolve a lot of addresses at assembly time, but not really foreign
// ones, since we aren't certain of their position.
//
/* TODO: Handle this for flat binary case i.e. no linker required. This may be
* done using a similar method to before, such as just concatenating all
* of the files together and handling jumps and halts.
*
* > Ask Harry or read the initial code.
*/
if self.dependencies.insert(dep) {
self.needs_relocation = true;
}
}
/// Returns whether a [`Symbol`] depends on `symbol_id`.
#[must_use]
pub fn depends_on(&self, symbol_id: &SymbolId) -> bool {
self.dependencies.contains(symbol_id)
}
/// Removes a [`Symbol`] from the dependency set.
pub fn remove_dependency(&mut self, symbol_id: &SymbolId) {
self.dependencies.remove(symbol_id);
if self.dependencies.is_empty() {
self.needs_relocation = false;
}
}
}
#[derive(Debug, Copy, Clone)]
/// The visibility of the symbol in different object files.
pub enum Visibility {
/// `STB_PUBLIC` under the ELF spec. Visible in all other object files. Shall be used
/// for labels. Remember labels are namespaced in different files so they won't clash
/// with one another.
Public,
/// Only visible within this object file. `STB_LOCAL` under ELF spec. Shall be used
/// for data definitions unless they are marked public.
Local,
/// `STB_WEAK` under the ELF spec. Potentially unused.
Weak,
}
#[derive(Debug)]
pub enum SymbolType {
LabelOrFunction,
Variable,
}
+29
View File
@@ -0,0 +1,29 @@
//! This module contains anything within the first stage of assembly, i.e. the
//! tokenisation stage, or utility functions for reading input files.
use std::{
io::{BufRead, Lines},
path::Path,
};
use crate::error::AssembleError;
pub mod lines;
pub mod opcode;
pub mod source_info;
pub mod token;
pub mod token_info;
pub mod tokeniser;
/// Attempts to load and open a source file, returning a [`Vec<u8>`] or an
/// [`AssembleError`].
pub fn load_source_bytes<P: AsRef<Path>>(p: P) -> Result<Vec<u8>, AssembleError> {
let path = p.as_ref();
Ok(std::fs::read(path)?)
}
/// Get the lines from a [`BufReader`].
pub fn reader_lines<R: BufRead>(rdr: R) -> Lines<R> {
rdr.lines()
}
+76
View File
@@ -0,0 +1,76 @@
//! Enhanced lines iterator that tracks line numbers and character positions.
use std::io::{BufRead, BufReader, Cursor};
use crate::error::AssembleError;
/// Iterator that yields lines with their line numbers and character spans.
pub struct LinesWithSpans<R: BufRead> {
reader: R,
line_number: usize,
total_chars: usize,
buffer: String,
}
#[derive(Debug, Clone)]
pub struct LineSpan {
/// The line number.
pub line_number: usize,
/// The contents of the line.
pub content: String,
/// Character offset from start of file.
pub start_char: usize,
/// End character offset (exclusive).
pub end_char: usize,
}
impl<R: BufRead> LinesWithSpans<R> {
pub const fn new(reader: R) -> Self {
Self {
reader,
line_number: 0,
total_chars: 0,
buffer: String::new(),
}
}
}
impl<R: BufRead> Iterator for LinesWithSpans<R> {
type Item = Result<LineSpan, AssembleError>;
fn next(&mut self) -> Option<Self::Item> {
self.buffer.clear();
match self.reader.read_line(&mut self.buffer) {
Ok(0) => None, // EOF
Ok(bytes_read) => {
self.line_number += 1;
let start_char = self.total_chars;
self.total_chars += bytes_read;
// Remove trailing newline for cleaner processing
let content = if self.buffer.ends_with('\n') {
self.buffer[..self.buffer.len() - 1].to_string()
} else {
self.buffer.clone()
};
Some(Ok(LineSpan {
line_number: self.line_number,
content,
start_char,
end_char: self.total_chars,
}))
}
Err(e) => Some(Err(e.into())),
}
}
}
/// Helper function to create lines iterator from data.
#[must_use]
pub fn lines_with_spans(data: &[u8]) -> LinesWithSpans<BufReader<Cursor<&[u8]>>> {
let cursor = Cursor::new(data);
let reader = BufReader::new(cursor);
LinesWithSpans::new(reader)
}
@@ -1,84 +1,285 @@
//! This module contains instructions for tokenisation.
use std::{fmt, str::FromStr};
use common::prelude::Register;
use common::prelude::{ITypeArgs, Instruction, Interrupt, RTypeArgs};
use crate::assembler::AssembleError;
use crate::{
error::{AssembleError, AssembleErrorKind},
source::source_info::SourceInfo,
};
#[derive(Debug, Clone)]
pub struct Node {
pub symbol: Option<Symbol>,
pub opcode: Opcode,
pub tokens: Vec<Token>,
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Opcode {
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,
}
impl Node {
#[must_use]
pub const fn new(symbol: Option<Symbol>, opcode: Opcode, tokens: Vec<Token>) -> Self {
Self {
symbol,
opcode,
tokens,
}
}
#[derive(Debug)]
pub enum OpcodeFromStrError {
InvalidRegister(&'static str),
InvalidOpcode(String),
}
#[must_use]
pub fn label(&self) -> Option<Symbol> {
self.symbol.clone()
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}"),
}
#[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"));
impl std::error::Error for OpcodeFromStrError {}
let args = self
.args()
.into_iter()
.map(|arg| arg.to_string())
.collect::<Vec<_>>()
.join(" ");
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",
];
write!(
f,
"\x1b[93m{} \t\x1b[94m{} \x1b[37m{} \x1b[0m",
symbol,
self.opcode(),
args,
pub fn to_instruction(
&self,
source_info: SourceInfo,
) -> Result<Instruction, AssembleError> {
match self {
Self::Nop => Ok(Instruction::Nop),
Self::Mov => Ok(Instruction::Mov(RTypeArgs::default())),
Self::Movs => Ok(Instruction::MovSigned(RTypeArgs::default())),
Self::Ldb => Ok(Instruction::LoadByte(ITypeArgs::default())),
Self::Ldbs => Ok(Instruction::LoadByteSigned(ITypeArgs::default())),
Self::Ldh => Ok(Instruction::LoadHalfword(ITypeArgs::default())),
Self::Ldhs => Ok(Instruction::LoadHalfwordSigned(ITypeArgs::default())),
Self::Ldw => Ok(Instruction::LoadWord(ITypeArgs::default())),
Self::Stb => Ok(Instruction::StoreByte(ITypeArgs::default())),
Self::Sth => Ok(Instruction::StoreHalfword(ITypeArgs::default())),
Self::Stw => Ok(Instruction::StoreWord(ITypeArgs::default())),
Self::Lli => Ok(Instruction::LoadLowerImmediate(ITypeArgs::default())),
Self::Lui => Ok(Instruction::LoadUpperImmediate(ITypeArgs::default())),
Self::Jmp => Ok(Instruction::Jump(ITypeArgs::default())),
Self::Jeq => Ok(Instruction::JumpEq(ITypeArgs::default())),
Self::Jne => Ok(Instruction::JumpNeq(ITypeArgs::default())),
Self::Jgt => Ok(Instruction::JumpGt(ITypeArgs::default())),
Self::Jge => Ok(Instruction::JumpGe(ITypeArgs::default())),
Self::Jlt => Ok(Instruction::JumpLt(ITypeArgs::default())),
Self::Jle => Ok(Instruction::JumpLe(ITypeArgs::default())),
Self::Cmp => Ok(Instruction::Compare(RTypeArgs::default())),
Self::Inc => Ok(Instruction::Increment(RTypeArgs::default())),
Self::Dec => Ok(Instruction::Decrement(RTypeArgs::default())),
Self::Shl => Ok(Instruction::ShiftLeft(RTypeArgs::default())),
Self::Shr => Ok(Instruction::ShiftRight(RTypeArgs::default())),
Self::Add => Ok(Instruction::Add(RTypeArgs::default())),
Self::Sub => Ok(Instruction::Sub(RTypeArgs::default())),
Self::And => Ok(Instruction::And(RTypeArgs::default())),
Self::Or => Ok(Instruction::Or(RTypeArgs::default())),
Self::Not => Ok(Instruction::Not(RTypeArgs::default())),
Self::Xor => Ok(Instruction::Xor(RTypeArgs::default())),
Self::Nand => Ok(Instruction::Nand(RTypeArgs::default())),
Self::Nor => Ok(Instruction::Nor(RTypeArgs::default())),
Self::Xnor => Ok(Instruction::Xnor(RTypeArgs::default())),
Self::Int => Ok(Instruction::Interrupt(Interrupt::default())),
Self::Irt => Ok(Instruction::IntReturn),
Self::Hlt => Ok(Instruction::Halt),
Self::AddI => Ok(Instruction::AddImmediate(ITypeArgs::default())),
Self::SubI => Ok(Instruction::SubImmediate(ITypeArgs::default())),
Self::Segment => Ok(Instruction::Segment(0)),
_ => Err(AssembleError::new_source_error(
source_info,
AssembleErrorKind::Unimplemented(
"Opcode::to_instruction called on an instruction that does not exist in common.",
),
)),
}
}
#[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),
// TODO: Maybe recombine pseudos?
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
)
}
}
impl fmt::Display for Symbol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} [ID:{}]", self.name, self.module)
}
}
impl FromStr for Opcode {
type Err = OpcodeFromStrError;
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}"),
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())),
}
}
}
@@ -146,293 +347,3 @@ impl fmt::Display for Opcode {
}
}
}
#[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
)
}
}
+4
View File
@@ -0,0 +1,4 @@
//! This module contains code for handling pseudo opcodes.
/// Pseudo instructions that cannot simply be lowered to ISA instructions.
pub enum PseudoOpcode {}
+104
View File
@@ -0,0 +1,104 @@
//! This file contains information on where a [`Token`] or [`Node`] is within the source
//! code for more informative errors.
//!
//! This will likely be attached to a [`Token`] which will in turn be attached to an AST
//! [`Node`].
use std::{
fmt::{Display, Write},
fs::File,
io::BufReader,
sync::Arc,
};
use crate::{
error::{AssembleError, AssembleErrorKind, IoError, IoErrorKind},
model::module::Module,
source::lines::LinesWithSpans,
};
/// Information on where the token is within the source.
#[derive(Debug, Clone)]
pub struct SourceInfo {
/// The line number within the source file underpinned by `module_id`.
pub line_number: usize,
/// The [`Module`] the source code is associated with.
pub module: Arc<Module>,
/// The indexes where this token may be found (line-local).
pub span: std::ops::Range<usize>,
}
impl Display for SourceInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}:{}",
self.module.path.display(),
self.line_number,
self.span.start + 1
)
}
}
impl SourceInfo {
#[must_use]
pub const fn new(
line_no: usize,
module: Arc<Module>,
span: std::ops::Range<usize>,
) -> Self {
Self {
line_number: line_no,
module,
span,
}
}
/// Prints out where in the source code the error originated with an underline similar
/// to what rustc does.
pub fn print_context_with_underline(&self) -> Result<(), AssembleError> {
let f = File::open(&self.module.path)?;
let rdr = BufReader::new(f);
let mut lines = LinesWithSpans::new(rdr);
let Some(line_result) = lines.nth(self.line_number - 1) else {
// Handle a line not existing.
return Err(AssembleError::new_source_error(
self.clone(),
AssembleErrorKind::Io(IoError::new(
IoErrorKind::Other,
Some(format!(
"the line {} does not exist in input file `{}` but source info suggested otherwise!.",
self.line_number,
self.module.path.display()
)),
)),
));
};
let line_span = line_result?;
// Print the line number and line content.
println!("{:>4} | {}", self.line_number, line_span.content);
let mut pad_left = String::new();
write!(pad_left, "{:>4} ", "")?;
let mut underline = String::new();
for _ in 0..self.span.start {
pad_left.push(' ');
}
for _ in self.span.start..self.span.end.min(line_span.content.len()) {
underline.push('^');
}
// Print the underline in red and bold.
// TODO: Use a crate to make this extra portable.
println!("{pad_left}\x1b[1;31m{underline}\x1b[0m");
Ok(())
}
}
+91
View File
@@ -0,0 +1,91 @@
//! Contains [`TokenType`] and [`Token`]'s. Adapted from Harry's old lexer since it was
//! easier to build from scratch and edit his code than it would be to try and wrangle it
//! into shape.
use common::prelude::*;
use crate::source::{
opcode::Opcode,
source_info::SourceInfo,
token_info::{DirectiveToken, LabelToken, RegisterToken, SymbolToken},
};
/// Represents the different types of tokens that can be produced by the tokeniser.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TokenType {
/// Symbol reference (e.g., `loop_start`, `my_data`).
Symbol(SymbolToken),
/// CPU register (e.g., `r1`, `r2`, `sp`).
Register(RegisterToken),
/// Immediate value (e.g., `42`, `0xFF`).
Immediate(u32),
/// String literal (e.g., `"hello world"`).
String(String),
/// Intermediate token for multiline strings (filtered out in final output)
StringContinuation,
/// Assembly instruction (e.g., `add`, `jmp`, `nop`).
Instruction(Opcode),
/// Label definition (e.g., `loop_start:`).
Label(LabelToken),
/// Assembler directive (e.g., `.global`, `.section`, `.dw`).
Directive(DirectiveToken),
/// Comment (e.g., `// this is a comment`).
Comment,
/// Comma separator.
Comma,
/// End of line.
Newline,
/// End of file.
Eof,
}
#[derive(Debug)]
pub struct Token {
/// The type of the token.
pub token_type: TokenType,
/// Where in the source code is this [`Token`]?
pub source_info: SourceInfo,
}
impl Token {
#[must_use]
pub const fn new(token_type: TokenType, source_info: SourceInfo) -> Self {
Self {
token_type,
source_info,
}
}
#[must_use]
pub const fn symbol(name: String, source_info: SourceInfo) -> Self {
Self::new(TokenType::Symbol(SymbolToken { name }), source_info)
}
#[must_use]
pub const fn label(name: String, source_info: SourceInfo) -> Self {
Self::new(TokenType::Label(LabelToken { name }), source_info)
}
#[must_use]
pub const fn instruction(op: Opcode, source_info: SourceInfo) -> Self {
Self::new(TokenType::Instruction(op), source_info)
}
#[must_use]
pub const fn register(reg: Register, source_info: SourceInfo) -> Self {
Self::new(TokenType::Register(RegisterToken { reg }), source_info)
}
#[must_use]
pub const fn immediate(value: u32, source_info: SourceInfo) -> Self {
Self::new(TokenType::Immediate(value), source_info)
}
#[must_use]
pub const fn directive(directive: String, source_info: SourceInfo) -> Self {
Self::new(
TokenType::Directive(DirectiveToken { directive }),
source_info,
)
}
}
+34
View File
@@ -0,0 +1,34 @@
use common::prelude::Register;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SymbolToken {
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct LabelToken {
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DirectiveToken {
pub directive: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RegisterToken {
pub reg: Register,
}
impl RegisterToken {
#[must_use]
pub const fn new(reg: Register) -> Self {
Self { reg }
}
/// Returns the name of a valid [`Register`]
#[must_use]
pub fn name(&self) -> String {
self.reg.to_string()
}
}
+421
View File
@@ -0,0 +1,421 @@
//! This file contains the [`Tokeniser`], which consumes a [`Vec`] of input bytes and
//! outputs a [`Vec<Token>`].
use std::{path::Path, str::FromStr, sync::Arc};
use regex::Regex;
use common::prelude::*;
use crate::{
context::AssemblerContext,
error::{AssembleError, AssembleErrorKind},
model::module::Module,
source::{
lines::{LineSpan, lines_with_spans},
load_source_bytes,
opcode::Opcode,
source_info::SourceInfo,
token::{Token, TokenType},
token_info::{DirectiveToken, LabelToken, RegisterToken, SymbolToken},
tokeniser::error::TokeniserError,
},
};
pub mod error;
#[cfg(test)]
mod tests;
/// Consumes a [`Vec<u8>`] and outputs a [`Vec`] of [Token]'s.
pub struct Tokeniser {
/// The data in the file.
pub data: Vec<u8>,
/// A copy of the Module in which the file is situated.
pub module: Arc<Module>,
// Pre-compiled regex patterns
label_regex: Regex,
register_regex: Regex,
immediate_regex: Regex,
directive_regex: Regex,
instruction_regex: Regex,
symbol_regex: Regex,
comment_regex: Regex,
// String parsing state
in_string: bool,
string_buffer: String,
string_start_line: usize,
string_start_column: usize,
}
impl Tokeniser {
#[must_use]
pub fn from_data(data: Vec<u8>, module: Arc<Module>) -> Self {
Self {
data,
module,
label_regex: Regex::new(r"^([a-zA-Z_][a-zA-Z0-9_]*):")
.expect("Failed to compile label regex pattern"),
register_regex: Regex::new(
r"^(rg[0-9a-f]+|acc|spr|bpr|ret|idr|mmr|zero|noreg|pcx)\b",
)
.expect("Failed to compile register regex pattern"),
immediate_regex: Regex::new(
r"^(0x[0-9a-fA-F_]+|0b[0-1_]+|0o[0-7_]+|[0-9_]+)",
)
.expect("Failed to compile immediate regex pattern"),
directive_regex: Regex::new(r"^(res[bwh]|d[bwh]|include|section|global|local)\b")
.expect("Failed to compile directive regex pattern"),
instruction_regex: Regex::new(
r"^(nop|movs?|ld[bhw]s?|st[bhw]|l[lu]i|j(mp|[egl][qte])|cmp|[id]nc|sh[lr]|add[i]?|sub[i]?|x?n?or|and|not|i[rd]t|hlt|lhwmm|lidt|push[a]?|pop[a]?|lwi|return|call)\b",
)
.expect("Failed to compile instruction regex pattern"),
symbol_regex: Regex::new(r"^([a-zA-Z_][a-zA-Z0-9_]*)::{2}([a-zA-Z0-9_]*)|([a-zA-Z_][a-zA-Z0-9_]*)")
.expect("Failed to compile symbol regex pattern"),
comment_regex: Regex::new("^//.*")
.expect("Failed to compile comment regex pattern"),
// Initialize string parsing state
in_string: false,
string_buffer: String::new(),
string_start_line: 0,
string_start_column: 0,
}
}
/// Creates a [`Tokeniser`] from a file path. Also creates the underlying [`Module`]
/// for you.
pub fn new<P: AsRef<Path>>(
path: P,
ctx: &AssemblerContext,
) -> Result<Self, AssembleError> {
let path = path.as_ref().to_path_buf();
let data = load_source_bytes(&path)?;
let module = Arc::new(Module::new(path)?);
{
let mut module_registry = ctx.module_registry.write()?;
module_registry.add(module.clone());
}
Ok(Self::from_data(data, module))
}
// Note that modules are tokenised in their own threads, possibly in parallel.
pub fn tokenise(mut self) -> Result<Vec<Token>, AssembleError> {
let mut token_stream = Vec::new();
let data = self.data.clone();
let lines = lines_with_spans(&data);
// Process each line
for line_result in lines {
let line_span = line_result?;
let trimmed = line_span.content.trim();
// Skip empty lines and add newline tokens
if trimmed.is_empty() {
token_stream.push(Token::new(
TokenType::Newline,
SourceInfo::new(line_span.line_number, self.module.clone(), 0..1),
));
continue;
}
// Actually tokenise the line content
let line_tokens = self.tokenise_line(&line_span)?;
token_stream.extend(line_tokens);
// Add newline token at end of line
token_stream.push(Token::new(
TokenType::Newline,
SourceInfo::new(
line_span.line_number,
self.module.clone(),
line_span.content.len()..line_span.content.len(),
),
));
}
// Add EOF token
token_stream.push(Token::new(
TokenType::Eof,
SourceInfo::new(0, self.module.clone(), 0..0),
));
Ok(token_stream)
}
fn tokenise_line(
&mut self,
line_span: &LineSpan,
) -> Result<Vec<Token>, AssembleError> {
let mut tokens = Vec::new();
let mut remaining = line_span.content.as_str();
let mut column = 0;
// Skip leading whitespace
let trimmed_start = remaining.trim_start();
column += remaining.len() - trimmed_start.len();
remaining = trimmed_start;
while !remaining.is_empty() {
let start_column = column;
// Try to match a token.
let (token_type, consumed) =
self.match_token(remaining, line_span.line_number, column)?;
// Filter out string continuation tokens and comments.
match token_type {
TokenType::StringContinuation => {
// Don't add to token stream, just consume input
}
TokenType::Comment => {
// Don't add to token stream, consume rest of line
break;
}
_ => {
tokens.push(Token::new(
token_type,
SourceInfo::new(
line_span.line_number,
self.module.clone(),
start_column..start_column + consumed,
),
));
}
}
// Advance position.
remaining = &remaining[consumed..];
column += consumed;
// Skip whitespace.
let before_trim = remaining.len();
remaining = remaining.trim_start();
column += before_trim - remaining.len();
}
Ok(tokens)
}
fn try_match_comment(&self, input: &str) -> Option<(TokenType, usize)> {
let caps = self.comment_regex.captures(input)?;
let len = caps.get(0)?.len();
Some((TokenType::Comment, len))
}
fn try_match_label(&self, input: &str) -> Option<(TokenType, usize)> {
let caps = self.label_regex.captures(input)?;
let name = caps.get(1)?.as_str().to_string();
let len = caps.get(0)?.len();
Some((TokenType::Label(LabelToken { name }), len))
}
fn try_match_register(&self, input: &str) -> Option<(TokenType, usize)> {
let caps = self.register_regex.captures(input)?;
let captured_group = caps.get(1)?.as_str();
let len = caps.get(0)?.len();
let reg = Register::try_from(captured_group).ok()?;
Some((TokenType::Register(RegisterToken { reg }), len))
}
fn try_match_immediate(&self, input: &str) -> Option<(TokenType, usize)> {
let caps = self.immediate_regex.captures(input)?;
let value_str = caps.get(1)?.as_str();
let len = caps.get(0)?.len();
// Remove any underscores that were inserted for readability.
let value_str = value_str.replace('_', "");
let value = if let Some(hex_part) = value_str.strip_prefix("0x") {
u32::from_str_radix(hex_part, 16).ok()?
} else if let Some(bin_part) = value_str.strip_prefix("0b") {
u32::from_str_radix(bin_part, 2).ok()?
} else if let Some(oct_part) = value_str.strip_prefix("0o") {
u32::from_str_radix(oct_part, 8).ok()?
} else {
value_str.parse::<u32>().ok()?
};
Some((TokenType::Immediate(value), len))
}
fn try_match_directive(&self, input: &str) -> Option<(TokenType, usize)> {
let caps = self.directive_regex.captures(input)?;
let directive = caps.get(1)?.as_str().to_string();
let len = caps.get(0)?.len();
Some((TokenType::Directive(DirectiveToken { directive }), len))
}
fn try_match_instruction(&self, input: &str) -> Option<(TokenType, usize)> {
let caps = self.instruction_regex.captures(input)?;
let mnemonic = caps.get(1)?.as_str().to_string();
let len = caps.get(0)?.len();
let op = Opcode::from_str(&mnemonic).ok()?;
Some((TokenType::Instruction(op), len))
}
fn try_match_symbol(&self, input: &str) -> Option<(TokenType, usize)> {
let caps = self.symbol_regex.captures(input)?;
let len = caps.get(0)?.len();
// Check which capture group matched.
let name = if let Some(scoped_name) = caps.get(1) {
// Matched the scoped symbol pattern (name::scope).
format!("{}::{}", scoped_name.as_str(), caps.get(2)?.as_str())
} else if let Some(simple_name) = caps.get(3) {
simple_name.as_str().to_string()
} else {
return None;
};
Some((TokenType::Symbol(SymbolToken { name }), len))
}
fn try_match_string(
&mut self,
input: &str,
line_number: usize,
column: usize,
) -> Option<(TokenType, usize)> {
if self.in_string {
// We're continuing a multiline string
Some(self.handle_string_continuation(input, line_number, column))
} else {
// Look for the start of a new string
self.handle_string_start(input, line_number, column)
}
}
fn handle_string_start(
&mut self,
input: &str,
line_number: usize,
column: usize,
) -> Option<(TokenType, usize)> {
if !input.starts_with('"') {
return None;
}
// Find the closing quote on the same line
if let Some(end_pos) = input[1..].find('"') {
// Complete string on one line
let content = input[1..=end_pos].to_string();
let len = end_pos + 2; // +2 for both quotes
Some((TokenType::String(content), len))
} else {
// Start of multiline string
self.in_string = true;
self.string_start_line = line_number;
self.string_start_column = column;
self.string_buffer = input[1..].to_string(); // Everything after opening quote
self.string_buffer.push('\n'); // Add newline for multiline
// Consume the entire rest of the line
Some((TokenType::StringContinuation, input.len()))
}
}
fn handle_string_continuation(
&mut self,
input: &str,
_line_number: usize,
_column: usize,
) -> (TokenType, usize) {
// Look for closing quote
if let Some(end_pos) = input.find('"') {
// End of multiline string found
self.string_buffer.push_str(&input[..end_pos]);
self.in_string = false;
let content = std::mem::take(&mut self.string_buffer);
let len = end_pos + 1; // +1 for the closing quote
(TokenType::String(content), len)
} else {
// Continue multiline string
self.string_buffer.push_str(input);
self.string_buffer.push('\n'); // Add newline
// Consume the entire line
(TokenType::StringContinuation, input.len())
}
}
#[expect(clippy::range_plus_one, reason = "RangeInclusive is a different type!")]
fn match_token(
&mut self,
input: &str,
line_number: usize,
column: usize,
) -> Result<(TokenType, usize), AssembleError> {
if input.starts_with(',') {
return Ok((TokenType::Comma, 1));
}
// Check for string first (including multiline continuations).
if let Some(m) = self.try_match_string(input, line_number, column) {
return Ok(m);
}
if let Some(m) = self.try_match_directive(input) {
return Ok(m);
}
if let Some(m) = self.try_match_instruction(input) {
return Ok(m);
}
if let Some(m) = self.try_match_comment(input) {
return Ok(m);
}
if let Some(m) = self.try_match_label(input) {
return Ok(m);
}
if let Some(m) = self.try_match_register(input) {
return Ok(m);
}
if let Some(m) = self.try_match_immediate(input) {
return Ok(m);
}
if let Some(m) = self.try_match_symbol(input) {
return Ok(m);
}
let mut idx_iter = (column + 1)..;
let Some(idx) = idx_iter.next() else {
unreachable!()
};
let source = SourceInfo::new(line_number, self.module.clone(), idx..idx + 1);
// Handle miscellaneous characters.
if let Some(c) = input.chars().next() {
Err(AssembleError::new_source_error(
source,
AssembleErrorKind::Tokeniser(TokeniserError::UnexpectedChar(c)),
))
} else {
Err(AssembleError::new_source_error(
source,
AssembleErrorKind::Tokeniser(TokeniserError::UnexpectedEndOfInput(
input.len(),
)),
))
}
}
}
+41
View File
@@ -0,0 +1,41 @@
//! This module contains the error types for the tokeniser.
#[derive(Debug, Clone, Copy)]
/// Types of errors that may be returned during tokenisation.
pub enum TokeniserError {
/// An unexpected character was found in the source code.
UnexpectedChar(char),
/// An unterminated string literal was found. [`SourceInfo`] will be attached if this
/// was returned.
UnterminatedString,
/// An invalid number format was encountered when parsing a literal value
/// ([`TokenType::Immediate`]).
InvalidNumber(&'static str),
/// An unrecognized token was encountered.
UnrecognisedToken,
/// Returned if the consumed count was lower than the length of the input file.
/// This is a sign you will need to debug some [`Tokeniser`] code to ensure that
/// [`Tokeniser::match_token`] is working as intended.
///
/// First field is length of the line.
UnexpectedEndOfInput(usize),
}
impl TokeniserError {}
impl std::fmt::Display for TokeniserError {
#[rustfmt::skip]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnexpectedChar(c) => write!(f, "unexpected char '{c}' found in input")?,
Self::InvalidNumber(lit) => write!(f, "invalid integer literal \"{lit}\" found in input")?,
Self::UnrecognisedToken => write!(f, "unrecognised token found in input")?,
Self::UnterminatedString => write!(f, "unterminated string literal")?,
Self::UnexpectedEndOfInput(line_length) => write!(
f, "unexpected end of input, input length: {line_length}"
)?,
}
Ok(())
}
}
+418
View File
@@ -0,0 +1,418 @@
//! Unit tests for the tokenizer
use common::prelude::Register;
use crate::{
model::module::Module,
source::{
opcode::Opcode,
token::{Token, TokenType},
token_info::RegisterToken,
tokeniser::Tokeniser,
},
};
use std::{path::PathBuf, sync::Arc};
/// Helper function to create a tokenizer from source text
fn create_tokenizer_from_source(source: &str) -> Tokeniser {
let path = PathBuf::from("test.dsa");
let module = Module::new(path).expect("Cannot create module!");
Tokeniser::from_data(source.as_bytes().to_vec(), Arc::new(module))
}
/// Helper function to tokenize source and return tokens
fn tokenize_source(source: &str) -> Result<Vec<Token>, crate::error::AssembleError> {
let tokenizer = create_tokenizer_from_source(source);
tokenizer.tokenise()
}
/// Helper function to extract token types from a token vector
fn extract_token_types(tokens: &[Token]) -> Vec<&TokenType> {
tokens.iter().map(|t| &t.token_type).collect()
}
#[test]
fn test_empty_source() {
let tokens = tokenize_source("").expect("Failed to tokenize empty source");
// Should have at least EOF token
assert!(!tokens.is_empty());
assert!(matches!(
tokens
.last()
.expect("Expected at least one token")
.token_type,
TokenType::Eof
));
}
#[test]
fn test_whitespace_only() {
let tokens = tokenize_source(" \n \n ").expect("Failed to tokenize whitespace");
// Should have newlines and EOF
let token_types = extract_token_types(&tokens);
assert!(token_types.iter().any(|t| matches!(t, TokenType::Newline)));
assert!(token_types.iter().any(|t| matches!(t, TokenType::Eof)));
}
#[test]
fn test_single_instruction() {
let tokens = tokenize_source("add").expect("Failed to tokenize instruction");
let token_types = extract_token_types(&tokens);
// Should have instruction, newline, and EOF
assert!(
token_types
.iter()
.any(|t| matches!(t, TokenType::Instruction(_)))
);
if let TokenType::Instruction(instr) = &tokens[0].token_type {
assert_eq!(instr.to_string(), "add");
} else {
panic!("Expected instruction token");
}
}
#[test]
fn test_all_instructions() {
let instructions = ["add", "sub", "jmp", "call", "return", "lli", "nop", "hlt"];
for instr in &instructions {
let tokens = tokenize_source(instr).expect("Failed to tokenize instruction");
if let TokenType::Instruction(parsed_instr) = &tokens[0].token_type {
assert_eq!(parsed_instr.to_string(), *instr);
} else {
panic!("Expected instruction token for {instr}");
}
}
}
#[test]
fn test_registers() {
let test_cases = [("rg0", "rg0"), ("rgf", "rgf"), ("pcx", "pcx")];
for (input, expected) in &test_cases {
let tokens = tokenize_source(input).expect("Failed to tokenize register");
if let TokenType::Register(reg) = &tokens[0].token_type {
assert_eq!(reg.reg.to_string(), *expected);
} else {
panic!("Expected register token for {input}");
}
}
}
#[test]
fn test_immediates() {
let test_cases = [
("42", 42),
("0", 0),
("0xFF", 255),
("0x1234", 0x1234),
("0xDEADBEEF", 0xDEAD_BEEF),
("0o12", 0o12),
("0b101", 0b101),
];
for (input, expected) in &test_cases {
let tokens = tokenize_source(input).expect("Failed to tokenize immediate");
if let TokenType::Immediate(value) = &tokens[0].token_type {
assert_eq!(*value, *expected);
} else {
panic!("Expected immediate token for {input}");
}
}
}
#[test]
fn test_labels() {
let test_cases = [
("loop_start:", "loop_start"),
("main:", "main"),
("_private_label:", "_private_label"),
("Label123:", "Label123"),
];
for (input, expected) in &test_cases {
let tokens = tokenize_source(input).expect("Failed to tokenize label");
if let TokenType::Label(label) = &tokens[0].token_type {
assert_eq!(label.name, *expected);
} else {
panic!("Expected label token for {input}");
}
}
}
#[test]
fn test_directives() {
let test_cases = [
("global", "global"),
("section", "section"),
("local", "local"),
];
for (input, expected) in &test_cases {
let tokens = tokenize_source(input).expect("Failed to tokenize directive");
if let TokenType::Directive(directive) = &tokens[0].token_type {
assert_eq!(directive.directive, *expected);
} else {
panic!("Expected directive token for {input}");
}
}
}
#[test]
fn test_symbols() {
let test_cases = [
("my_symbol", "my_symbol"),
("_private", "_private"),
("Symbol123", "Symbol123"),
("camelCase", "camelCase"),
];
for (input, expected) in &test_cases {
let tokens = tokenize_source(input).expect("Failed to tokenize symbol");
if let TokenType::Symbol(symbol) = &tokens[0].token_type {
assert_eq!(symbol.name, *expected);
} else {
panic!("Expected symbol token for {input}");
}
}
}
#[test]
fn test_complex_instruction_line() {
let source = "addi rg1, rg2, 0xFF";
let tokens = tokenize_source(source).expect("Failed to tokenise complex instruction");
// Should have: instruction, register, comma, register, comma, immediate, newline, EOF
assert!(tokens.len() >= 6);
assert!(matches!(tokens[0].token_type, TokenType::Instruction(_)));
assert!(matches!(tokens[1].token_type, TokenType::Register(_)));
assert!(matches!(tokens[2].token_type, TokenType::Comma));
assert!(matches!(tokens[3].token_type, TokenType::Register(_)));
assert!(matches!(tokens[4].token_type, TokenType::Comma));
assert!(matches!(tokens[5].token_type, TokenType::Immediate(_)));
}
#[test]
fn test_multiline_with_comments() {
const EXPECTED_TOKEN_TYPES: [TokenType; 11] = [
TokenType::Instruction(Opcode::Add),
TokenType::Register(RegisterToken::new(Register::Rg0)),
TokenType::Comma,
TokenType::Register(RegisterToken::new(Register::Rg1)),
TokenType::Newline,
TokenType::Instruction(Opcode::SubI),
TokenType::Register(RegisterToken::new(Register::Rg2)),
TokenType::Comma,
TokenType::Immediate(10),
TokenType::Newline,
TokenType::Eof,
];
const SOURCE: &str = r"add rg0, rg1 // Another comment
subi rg2, 10";
let tokens =
tokenize_source(SOURCE).expect("Failed to tokenise source with comments");
let token_types = extract_token_types(&tokens);
assert_eq!(
token_types.len(),
EXPECTED_TOKEN_TYPES.len(),
"{token_types:#?}"
);
for (expected, got) in EXPECTED_TOKEN_TYPES.iter().zip(token_types.iter()) {
assert!(!(expected != *got), "Expected {expected:?}, got {got:?}");
}
}
#[test]
fn test_tokenise_brainf_interpreter() {
const SOURCE: &str = include_str!("../../../../resources/dsa/bf.dsa");
let tokens =
tokenize_source(SOURCE).expect("Failed to tokenise the brainfuck compiler!");
dbg!(tokens);
}
#[test]
fn test_string_literals() {
let test_cases = [
(r#""hello world""#, "hello world"),
(
r#""++++++++++++++++++++++++++++++++++++++++++++""#,
"++++++++++++++++++++++++++++++++++++++++++++",
),
(r#""Invalid Instruction!""#, "Invalid Instruction!"),
(r#""""#, ""),
];
for (input, expected) in &test_cases {
let tokens = tokenize_source(input).expect("Failed to tokenize string literal");
if let TokenType::String(value) = &tokens[0].token_type {
assert_eq!(value, expected);
} else {
panic!("Expected string token for {input}");
}
}
}
#[test]
fn test_data_directives() {
let test_cases = [("db", "db"), ("dw", "dw"), ("resb", "resb")];
for (input, expected) in &test_cases {
let tokens = tokenize_source(input).expect("Failed to tokenize data declaration");
if let TokenType::Directive(decl) = &tokens[0].token_type {
assert_eq!(decl.directive, *expected);
} else {
panic!("Expected data declaration token for {input}");
}
}
}
#[test]
fn test_include_directive() {
let source = r#"include print "./lib/print.dsa""#;
let tokens = tokenize_source(source).expect("Failed to tokenize include directive");
assert!(tokens.len() >= 3);
assert!(matches!(tokens[0].token_type, TokenType::Directive(_)));
assert!(matches!(tokens[1].token_type, TokenType::Symbol(_)));
assert!(matches!(tokens[2].token_type, TokenType::String(_)));
}
#[test]
fn test_hex_addresses() {
let test_cases = [("0x10000", 0x10000), ("0x30000", 0x30000)];
for (input, expected) in &test_cases {
let tokens = tokenize_source(input).expect("Failed to tokenize hex address");
if let TokenType::Immediate(value) = &tokens[0].token_type {
assert_eq!(*value, *expected);
} else {
panic!("Expected immediate token for {input}");
}
}
}
#[test]
fn test_memory_operations() {
let source = "ldw rg1, rg2";
let tokens = tokenize_source(source).expect("Failed to tokenize memory operation");
assert!(tokens.len() >= 4);
assert!(matches!(tokens[0].token_type, TokenType::Instruction(_)));
assert!(matches!(tokens[1].token_type, TokenType::Register(_)));
assert!(matches!(tokens[2].token_type, TokenType::Comma));
assert!(matches!(tokens[3].token_type, TokenType::Register(_)));
}
#[test]
fn test_function_calls() {
let source = "call print::print";
let tokens = tokenize_source(source).expect("Failed to tokenize function call");
assert!(tokens.len() >= 2);
assert!(matches!(tokens[0].token_type, TokenType::Instruction(_)));
// The symbol might be parsed differently depending on how :: is handled
// This test checks basic structure
assert!(
tokens
.iter()
.any(|t| matches!(t.token_type, TokenType::Symbol(_)))
);
}
#[test]
fn test_comments_are_ignored() {
let source = "add rg0, rg1 // this is a comment\nsub rg2, rg3";
let tokens = tokenize_source(source).expect("Failed to tokenize with comments");
// Comments should be stripped, so we should only have instruction tokens
let instruction_count = tokens
.iter()
.filter(|t| matches!(t.token_type, TokenType::Instruction(_)))
.count();
assert_eq!(instruction_count, 2);
}
#[test]
fn test_newline_always_present() {
// Test that even without explicit newline at end, one is added
let source = "add rg0, rg1"; // No newline at end
let tokens = tokenize_source(source).expect("Failed to tokenize without newline");
// Should have newline before EOF
let has_newline = tokens
.iter()
.any(|t| matches!(t.token_type, TokenType::Newline));
assert!(
has_newline,
"Expected newline to be added even when missing from input"
);
// EOF should be last.
assert!(matches!(
tokens
.last()
.expect("Expected at least one token")
.token_type,
TokenType::Eof
));
}
#[test]
fn test_complex_branching_code() {
let source = r"
cmp rg3, rg8
jeq increment
cmp rg3, rg9
jeq decrement";
let tokens = tokenize_source(source).expect("Failed to tokenize branching code");
let instruction_count = tokens
.iter()
.filter(|t| matches!(t.token_type, TokenType::Instruction(_)))
.count();
assert_eq!(instruction_count, 4);
let symbol_count = tokens
.iter()
.filter(|t| matches!(t.token_type, TokenType::Symbol(_)))
.count();
assert_eq!(symbol_count, 2); // increment and decrement labels
}
#[test]
fn test_stack_operations() {
let source = "push rg2\npop zero\npusha 2\npopa 2";
let tokens = tokenize_source(source).expect("Failed to tokenize stack operations");
let instruction_count = tokens
.iter()
.filter(|t| matches!(t.token_type, TokenType::Instruction(_)))
.count();
assert_eq!(instruction_count, 4);
}
+122
View File
@@ -0,0 +1,122 @@
//! This module contains the code for the Symbol Table, which can be written into object
//! files to support deferred relocations when using ELF files.
//!
//! It is also required for detection of duplicate symbols, and resolution in the flat
//! binary output type.
use crate::{
error::AssembleError,
model::{
module::ModuleId,
symbol::{Symbol, SymbolId, Visibility},
},
};
use std::collections::HashMap;
/// Global symbol table - single source of truth for all symbols.
/// Much simpler than per-module tables.
#[derive(Debug)]
pub struct SymbolTable {
/// All symbols by their ID - O(1) lookup
symbols: HashMap<SymbolId, Symbol>,
/// Name to ID mapping for human-readable lookups - O(1) lookup
name_to_id: HashMap<String, SymbolId>,
/// Module to symbols mapping for module-specific queries
module_symbols: HashMap<ModuleId, Vec<SymbolId>>,
}
impl SymbolTable {
#[must_use]
pub fn new() -> Self {
Self {
symbols: HashMap::new(),
name_to_id: HashMap::new(),
module_symbols: HashMap::new(),
}
}
/// Adds a symbol to the global table
pub fn add_symbol(&mut self, symbol: Symbol) -> Result<SymbolId, AssembleError> {
let id = symbol.id;
let module_id = symbol.module_id;
let name = symbol.name.clone();
// Check for duplicate names in the same module
if let Some(&existing_id) = self.name_to_id.get(&name)
&& let Some(existing) = self.symbols.get(&existing_id)
&& existing.module_id == module_id
{
return Err(std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
format!("Symbol '{name}' already defined in module"),
)
.into());
}
// Add to all mappings
self.name_to_id.insert(name, id);
self.symbols.insert(id, symbol);
self.module_symbols.entry(module_id).or_default().push(id);
Ok(id)
}
/// Gets the [`Symbol`] by its [`SymbolId`].
#[must_use]
pub fn get(&self, id: &SymbolId) -> Option<&Symbol> {
self.symbols.get(id)
}
/// Gets the [`Symbol`] by its name.
#[must_use]
pub fn get_by_name(&self, name: &str) -> Option<&Symbol> {
self.name_to_id
.get(name)
.and_then(|id| self.symbols.get(id))
}
/// Gets all [`Symbol`]s in a module.
#[must_use]
pub fn get_module_symbols(&self, module_id: &ModuleId) -> Vec<&Symbol> {
self.module_symbols
.get(module_id)
.map(|ids| ids.iter().filter_map(|id| self.symbols.get(id)).collect())
.unwrap_or_default()
}
/// Gets all the public symbols.
#[must_use]
pub fn get_public_symbols(&self) -> Vec<&Symbol> {
self.symbols
.values()
.filter(|sym| matches!(sym.visibility, Visibility::Public))
.collect()
}
/// Updates symbol address (during resolution). Used for flat binaries or symbols with
/// no relocations.
pub fn update_symbol_address(
&mut self,
id: &SymbolId,
address: u32,
) -> Result<(), AssembleError> {
if let Some(symbol) = self.symbols.get_mut(id) {
symbol.address = Some(address);
if symbol.dependencies.is_empty() {
symbol.needs_relocation = false;
}
Ok(())
} else {
Err(
std::io::Error::new(std::io::ErrorKind::NotFound, "Symbol not found")
.into(),
)
}
}
}
impl Default for SymbolTable {
fn default() -> Self {
Self::new()
}
}
+1
View File
@@ -2,6 +2,7 @@
#![allow(unused)]
use std::{fmt, sync::mpsc::Sender};
#[derive(Debug, PartialEq, Eq)]
pub struct Logger {}
impl Logger {
+1 -1
View File
@@ -2,7 +2,7 @@ pub mod logging;
use std::io::Write;
pub fn input(prompt: &str) -> String {
pub fn _input(prompt: &str) -> String {
print!("{prompt}\n > ");
std::io::stdout().flush().expect("Failed to flush stdout");
let mut input = String::new();
-8
View File
@@ -1,8 +0,0 @@
[package]
name = "c_compiler"
version.workspace = true
edition.workspace = true
authors.workspace = true
[dependencies]
chrono = "0.4.42"
-14
View File
@@ -1,14 +0,0 @@
int var_x = 5;
int factorial(int n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
int main() {
int result = var_x + factorial(5);
print(result);
return 0;
}
-926
View File
@@ -1,926 +0,0 @@
#!/usr/bin/env python3
"""
Simple C to DSA Assembly Compiler
Supports a subset of C including:
- int variables and functions
- Arithmetic operations (+, -, *, /)
- Comparisons (==, !=, <, >, <=, >=)
- If/else statements
- While loops
- Function calls
- Return statements
"""
import re
import sys
from typing import List, Dict, Optional, Tuple
from dataclasses import dataclass
from enum import Enum
from pprint import pprint
import json
class TokenType(Enum):
# Keywords
INT = "int"
IF = "if"
ELSE = "else"
WHILE = "while"
RETURN = "return"
# Identifiers and literals
IDENTIFIER = "IDENTIFIER"
NUMBER = "NUMBER"
# Operators
PLUS = "+"
MINUS = "-"
STAR = "*"
SLASH = "/"
ASSIGN = "="
EQ = "=="
NE = "!="
LT = "<"
GT = ">"
LE = "<="
GE = ">="
# Delimiters
LPAREN = "("
RPAREN = ")"
LBRACE = "{"
RBRACE = "}"
SEMICOLON = ";"
COMMA = ","
EOF = "EOF"
@dataclass
class Token:
type: TokenType
value: str
line: int
col: int
class Lexer:
def __init__(self, source: str):
self.source = source
self.pos = 0
self.line = 1
self.col = 1
self.tokens = []
def error(self, msg: str):
raise SyntaxError(f"Lexer error at line {self.line}, col {self.col}: {msg}")
def peek(self, offset: int = 0) -> Optional[str]:
pos = self.pos + offset
return self.source[pos] if pos < len(self.source) else None
def advance(self) -> Optional[str]:
if self.pos >= len(self.source):
return None
char = self.source[self.pos]
self.pos += 1
if char == "\n":
self.line += 1
self.col = 1
else:
self.col += 1
return char
def skip_whitespace(self):
while self.peek() and self.peek() in " \t\n\r":
self.advance()
def skip_comment(self):
if self.peek() == "/" and self.peek(1) == "/":
while self.peek() and self.peek() != "\n":
self.advance()
self.advance() # skip newline
def read_number(self) -> str:
num = ""
while self.peek() and self.peek().isdigit():
num += self.advance()
return num
def read_identifier(self) -> str:
ident = ""
while self.peek() and (self.peek().isalnum() or self.peek() == "_"):
ident += self.advance()
return ident
def tokenize(self) -> List[Token]:
keywords = {
"int": TokenType.INT,
"if": TokenType.IF,
"else": TokenType.ELSE,
"while": TokenType.WHILE,
"return": TokenType.RETURN,
}
while self.pos < len(self.source):
self.skip_whitespace()
self.skip_comment()
if self.pos >= len(self.source):
break
line, col = self.line, self.col
char = self.peek()
# Numbers
if char.isdigit():
num = self.read_number()
self.tokens.append(Token(TokenType.NUMBER, num, line, col))
# Identifiers and keywords
elif char.isalpha() or char == "_":
ident = self.read_identifier()
token_type = keywords.get(ident, TokenType.IDENTIFIER)
self.tokens.append(Token(token_type, ident, line, col))
# Two-character operators
elif char == "=" and self.peek(1) == "=":
self.advance()
self.advance()
self.tokens.append(Token(TokenType.EQ, "==", line, col))
elif char == "!" and self.peek(1) == "=":
self.advance()
self.advance()
self.tokens.append(Token(TokenType.NE, "!=", line, col))
elif char == "<" and self.peek(1) == "=":
self.advance()
self.advance()
self.tokens.append(Token(TokenType.LE, "<=", line, col))
elif char == ">" and self.peek(1) == "=":
self.advance()
self.advance()
self.tokens.append(Token(TokenType.GE, ">=", line, col))
# Single-character operators
elif char == "+":
self.advance()
self.tokens.append(Token(TokenType.PLUS, "+", line, col))
elif char == "-":
self.advance()
self.tokens.append(Token(TokenType.MINUS, "-", line, col))
elif char == "*":
self.advance()
self.tokens.append(Token(TokenType.STAR, "*", line, col))
elif char == "/":
self.advance()
self.tokens.append(Token(TokenType.SLASH, "/", line, col))
elif char == "=":
self.advance()
self.tokens.append(Token(TokenType.ASSIGN, "=", line, col))
elif char == "<":
self.advance()
self.tokens.append(Token(TokenType.LT, "<", line, col))
elif char == ">":
self.advance()
self.tokens.append(Token(TokenType.GT, ">", line, col))
elif char == "(":
self.advance()
self.tokens.append(Token(TokenType.LPAREN, "(", line, col))
elif char == ")":
self.advance()
self.tokens.append(Token(TokenType.RPAREN, ")", line, col))
elif char == "{":
self.advance()
self.tokens.append(Token(TokenType.LBRACE, "{", line, col))
elif char == "}":
self.advance()
self.tokens.append(Token(TokenType.RBRACE, "}", line, col))
elif char == ";":
self.advance()
self.tokens.append(Token(TokenType.SEMICOLON, ";", line, col))
elif char == ",":
self.advance()
self.tokens.append(Token(TokenType.COMMA, ",", line, col))
else:
self.error(f"Unexpected character: {char}")
self.tokens.append(Token(TokenType.EOF, "", self.line, self.col))
return self.tokens
# AST Node classes
@dataclass
class ASTNode:
pass
@dataclass
class Program(ASTNode):
declarations: List["Declaration"]
@dataclass
class Declaration(ASTNode):
pass
@dataclass
class FunctionDecl(Declaration):
name: str
params: List[str]
body: "CompoundStmt"
@dataclass
class VarDecl(Declaration):
name: str
init: Optional["Expression"] = None
@dataclass
class Statement(ASTNode):
pass
@dataclass
class CompoundStmt(Statement):
statements: List[Statement]
@dataclass
class ExprStmt(Statement):
expr: Optional["Expression"]
@dataclass
class IfStmt(Statement):
condition: "Expression"
then_stmt: Statement
else_stmt: Optional[Statement] = None
@dataclass
class WhileStmt(Statement):
condition: "Expression"
body: Statement
@dataclass
class ReturnStmt(Statement):
expr: Optional["Expression"]
@dataclass
class Expression(ASTNode):
pass
@dataclass
class BinaryOp(Expression):
op: str
left: Expression
right: Expression
@dataclass
class UnaryOp(Expression):
op: str
operand: Expression
@dataclass
class AssignExpr(Expression):
name: str
value: Expression
@dataclass
class VarExpr(Expression):
name: str
@dataclass
class NumberExpr(Expression):
value: int
@dataclass
class CallExpr(Expression):
name: str
args: List[Expression]
class Parser:
def __init__(self, tokens: List[Token]):
self.tokens = tokens
self.pos = 0
def error(self, msg: str):
token = self.current()
raise SyntaxError(f"Parser error at line {token.line}, col {token.col}: {msg}")
def current(self) -> Token:
return self.tokens[self.pos] if self.pos < len(self.tokens) else self.tokens[-1]
def peek(self, offset: int = 0) -> Token:
pos = self.pos + offset
return self.tokens[pos] if pos < len(self.tokens) else self.tokens[-1]
def advance(self) -> Token:
token = self.current()
if self.pos < len(self.tokens) - 1:
self.pos += 1
return token
def expect(self, token_type: TokenType) -> Token:
token = self.current()
if token.type != token_type:
self.error(f"Expected {token_type.value}, got {token.type.value}")
return self.advance()
def parse(self) -> Program:
declarations = []
while self.current().type != TokenType.EOF:
declarations.append(self.parse_declaration())
return Program(declarations)
def parse_declaration(self) -> Declaration:
self.expect(TokenType.INT)
name = self.expect(TokenType.IDENTIFIER).value
if self.current().type == TokenType.LPAREN:
# Function declaration
self.advance()
params = []
if self.current().type != TokenType.RPAREN:
self.expect(TokenType.INT)
params.append(self.expect(TokenType.IDENTIFIER).value)
while self.current().type == TokenType.COMMA:
self.advance()
self.expect(TokenType.INT)
params.append(self.expect(TokenType.IDENTIFIER).value)
self.expect(TokenType.RPAREN)
body = self.parse_compound_stmt()
return FunctionDecl(name, params, body)
else:
# Variable declaration
init = None
if self.current().type == TokenType.ASSIGN:
self.advance()
init = self.parse_expression()
self.expect(TokenType.SEMICOLON)
return VarDecl(name, init)
def parse_compound_stmt(self) -> CompoundStmt:
self.expect(TokenType.LBRACE)
statements = []
while self.current().type != TokenType.RBRACE:
statements.append(self.parse_statement())
self.expect(TokenType.RBRACE)
return CompoundStmt(statements)
def parse_statement(self) -> Statement:
token = self.current()
if token.type == TokenType.LBRACE:
return self.parse_compound_stmt()
elif token.type == TokenType.IF:
return self.parse_if_stmt()
elif token.type == TokenType.WHILE:
return self.parse_while_stmt()
elif token.type == TokenType.RETURN:
return self.parse_return_stmt()
elif token.type == TokenType.INT:
# Local variable declaration
self.advance()
name = self.expect(TokenType.IDENTIFIER).value
init = None
if self.current().type == TokenType.ASSIGN:
self.advance()
init = self.parse_expression()
self.expect(TokenType.SEMICOLON)
return ExprStmt(AssignExpr(name, init) if init else None)
else:
expr = (
self.parse_expression()
if self.current().type != TokenType.SEMICOLON
else None
)
self.expect(TokenType.SEMICOLON)
return ExprStmt(expr)
def parse_if_stmt(self) -> IfStmt:
self.expect(TokenType.IF)
self.expect(TokenType.LPAREN)
condition = self.parse_expression()
self.expect(TokenType.RPAREN)
then_stmt = self.parse_statement()
else_stmt = None
if self.current().type == TokenType.ELSE:
self.advance()
else_stmt = self.parse_statement()
return IfStmt(condition, then_stmt, else_stmt)
def parse_while_stmt(self) -> WhileStmt:
self.expect(TokenType.WHILE)
self.expect(TokenType.LPAREN)
condition = self.parse_expression()
self.expect(TokenType.RPAREN)
body = self.parse_statement()
return WhileStmt(condition, body)
def parse_return_stmt(self) -> ReturnStmt:
self.expect(TokenType.RETURN)
expr = None
if self.current().type != TokenType.SEMICOLON:
expr = self.parse_expression()
self.expect(TokenType.SEMICOLON)
return ReturnStmt(expr)
def parse_expression(self) -> Expression:
return self.parse_assignment()
def parse_assignment(self) -> Expression:
expr = self.parse_comparison()
if self.current().type == TokenType.ASSIGN:
if not isinstance(expr, VarExpr):
self.error("Invalid assignment target")
self.advance()
value = self.parse_assignment()
return AssignExpr(expr.name, value)
return expr
def parse_comparison(self) -> Expression:
expr = self.parse_additive()
while self.current().type in [
TokenType.EQ,
TokenType.NE,
TokenType.LT,
TokenType.GT,
TokenType.LE,
TokenType.GE,
]:
op = self.advance().value
right = self.parse_additive()
expr = BinaryOp(op, expr, right)
return expr
def parse_additive(self) -> Expression:
expr = self.parse_multiplicative()
while self.current().type in [TokenType.PLUS, TokenType.MINUS]:
op = self.advance().value
right = self.parse_multiplicative()
expr = BinaryOp(op, expr, right)
return expr
def parse_multiplicative(self) -> Expression:
expr = self.parse_unary()
while self.current().type in [TokenType.STAR, TokenType.SLASH]:
op = self.advance().value
right = self.parse_unary()
expr = BinaryOp(op, expr, right)
return expr
def parse_unary(self) -> Expression:
if self.current().type in [TokenType.PLUS, TokenType.MINUS]:
op = self.advance().value
operand = self.parse_unary()
return UnaryOp(op, operand)
return self.parse_primary()
def parse_primary(self) -> Expression:
token = self.current()
if token.type == TokenType.NUMBER:
self.advance()
return NumberExpr(int(token.value))
elif token.type == TokenType.IDENTIFIER:
name = self.advance().value
if self.current().type == TokenType.LPAREN:
# Function call
self.advance()
args = []
if self.current().type != TokenType.RPAREN:
args.append(self.parse_expression())
while self.current().type == TokenType.COMMA:
self.advance()
args.append(self.parse_expression())
self.expect(TokenType.RPAREN)
return CallExpr(name, args)
else:
return VarExpr(name)
elif token.type == TokenType.LPAREN:
self.advance()
expr = self.parse_expression()
self.expect(TokenType.RPAREN)
return expr
else:
self.error(f"Unexpected token: {token.type.value}")
class CodeGenerator:
def __init__(self):
self.output = []
self.label_counter = 0
self.string_counter = 0
self.functions = {}
self.current_function = None
self.local_vars = {}
self.global_vars = {}
self.register_pool = [f"rg{i:x}" for i in range(16)]
self.used_registers = set()
def new_label(self, prefix: str = "L") -> str:
label = f"{prefix}{self.label_counter}"
self.label_counter += 1
return label
def allocate_register(self) -> str:
for reg in self.register_pool:
if reg not in self.used_registers:
self.used_registers.add(reg)
return reg
raise RuntimeError("Out of registers")
def free_register(self, reg: str):
self.used_registers.discard(reg)
def emit(self, code: str):
self.output.append(code)
def generate(self, program: Program) -> str:
# Emit data section
self.emit("// Global variables")
for decl in program.declarations:
if isinstance(decl, VarDecl):
self.global_vars[decl.name] = f"var_{decl.name}"
if decl.init:
if isinstance(decl.init, NumberExpr):
self.emit(f"dw var_{decl.name}: {decl.init.value}")
else:
self.emit(f"dw var_{decl.name}: 0")
else:
self.emit(f"dw var_{decl.name}: 0")
self.emit("")
self.emit("// Entry point")
self.emit("dw stack_bottom: 0x10000")
self.emit("")
self.emit("init:")
self.emit(" ldw stack_bottom, spr")
self.emit(" mov spr, bpr")
self.emit(" push zero")
self.emit(" call main")
self.emit(" pop rg0")
self.emit(" hlt")
self.emit("")
# Emit functions
for decl in program.declarations:
if isinstance(decl, FunctionDecl):
self.generate_function(decl)
return "\n".join(self.output)
def generate_function(self, func: FunctionDecl):
self.current_function = func.name
self.functions[func.name] = func
self.local_vars = {}
# Map parameters to stack offsets
# Parameters start at bpr+8 (after return addr at bpr+4)
for i, param in enumerate(func.params):
self.local_vars[param] = 8 + (i * 4)
self.emit(f"{func.name}:")
self.emit(" push bpr")
self.emit(" mov spr, bpr")
self.emit("")
# Generate function body
self.generate_compound_stmt(func.body)
# Default return if no explicit return
self.emit("// default return")
self.emit(f"{func.name}_end:")
self.emit(" mov bpr, spr")
self.emit(" pop bpr")
self.emit(" return")
self.emit("")
def generate_compound_stmt(self, stmt: CompoundStmt):
for s in stmt.statements:
self.generate_statement(s)
def generate_statement(self, stmt: Statement):
if isinstance(stmt, CompoundStmt):
self.generate_compound_stmt(stmt)
elif isinstance(stmt, ExprStmt):
if stmt.expr:
reg = self.generate_expression(stmt.expr)
self.free_register(reg)
elif isinstance(stmt, IfStmt):
self.generate_if_stmt(stmt)
elif isinstance(stmt, WhileStmt):
self.generate_while_stmt(stmt)
elif isinstance(stmt, ReturnStmt):
self.generate_return_stmt(stmt)
def generate_if_stmt(self, stmt: IfStmt):
else_label = self.new_label("else")
end_label = self.new_label("endif")
# Evaluate condition
cond_reg = self.generate_expression(stmt.condition)
self.emit(f" cmp {cond_reg}, zero")
self.free_register(cond_reg)
if stmt.else_stmt:
self.emit(f" jeq {else_label}")
else:
self.emit(f" jeq {end_label}")
# Then branch
self.generate_statement(stmt.then_stmt)
if stmt.else_stmt:
self.emit(f" jmp {end_label}")
self.emit(f"{else_label}:")
self.generate_statement(stmt.else_stmt)
self.emit(f"{end_label}:")
def generate_while_stmt(self, stmt: WhileStmt):
start_label = self.new_label("while_start")
end_label = self.new_label("while_end")
self.emit(f"{start_label}:")
# Evaluate condition
cond_reg = self.generate_expression(stmt.condition)
self.emit(f" cmp {cond_reg}, zero")
self.free_register(cond_reg)
self.emit(f" jeq {end_label}")
# Loop body
self.generate_statement(stmt.body)
self.emit(f" jmp {start_label}")
self.emit(f"{end_label}:")
def generate_return_stmt(self, stmt: ReturnStmt):
if stmt.expr:
reg = self.generate_expression(stmt.expr)
# Store return value at spr+8 according to calling convention
self.emit(f" stw {reg}, spr, 8")
self.free_register(reg)
self.emit(f" jmp {self.current_function}_end")
def generate_expression(self, expr: Expression) -> str:
if isinstance(expr, NumberExpr):
reg = self.allocate_register()
if expr.value <= 0xFFFF and expr.value >= 0:
self.emit(f" lli {expr.value}, {reg}")
if expr.value > 0xFF:
self.emit(f" lui {expr.value >> 16}, {reg}")
else:
self.emit(f" lli {expr.value & 0xFFFF}, {reg}")
self.emit(f" lui {(expr.value >> 16) & 0xFFFF}, {reg}")
return reg
elif isinstance(expr, VarExpr):
reg = self.allocate_register()
if expr.name in self.local_vars:
offset = self.local_vars[expr.name]
self.emit(f" ldw bpr, {reg}, {offset}")
elif expr.name in self.global_vars:
label = self.global_vars[expr.name]
self.emit(f" ldw {label}, {reg}")
else:
raise RuntimeError(f"Undefined variable: {expr.name}")
return reg
elif isinstance(expr, AssignExpr):
value_reg = self.generate_expression(expr.value)
if expr.name in self.local_vars:
offset = self.local_vars[expr.name]
self.emit(f" stw {value_reg}, bpr, {offset}")
elif expr.name in self.global_vars:
label = self.global_vars[expr.name]
self.emit(f" stw {value_reg}, {label}")
else:
# New local variable - allocate after params and return value space
# Start local variables at offset -4 from bpr (growing downward)
offset = -(len([v for v in self.local_vars.values() if v < 0]) + 1) * 4
self.local_vars[expr.name] = offset
self.emit(f" stw {value_reg}, bpr, {offset}")
return value_reg
elif isinstance(expr, BinaryOp):
return self.generate_binary_op(expr)
elif isinstance(expr, UnaryOp):
operand_reg = self.generate_expression(expr.operand)
result_reg = self.allocate_register()
if expr.op == "-":
self.emit(f" lwi 0, {result_reg}")
self.emit(f" sub {result_reg}, {operand_reg}, {result_reg}")
else: # +
self.emit(f" mov {operand_reg}, {result_reg}")
self.free_register(operand_reg)
return result_reg
elif isinstance(expr, CallExpr):
# First, make space for return value (must be pushed BEFORE arguments)
temp_reg = self.allocate_register()
# Then push arguments in reverse order
arg_regs = []
for arg in reversed(expr.args):
reg = self.generate_expression(arg)
self.emit(f" push {reg}")
arg_regs.append(reg)
# Call function
self.emit(f" call {expr.name}")
# Get return value (it's now on top of stack)
self.emit(f" pop {temp_reg}")
# Clean up remaining args
for i in range(len(arg_regs) - 1):
self.emit(f" pop zero")
# Free the arg registers
for reg in arg_regs:
self.free_register(reg)
return temp_reg
else:
raise RuntimeError(f"Unknown expression type: {type(expr)}")
def generate_binary_op(self, expr: BinaryOp) -> str:
# For operations that might contain function calls, we need to be careful
# about register allocation. Evaluate left, save it, evaluate right.
left_reg = self.generate_expression(expr.left)
# If right side contains a function call, we need to save left_reg
# For now, always save to be safe
saved_reg = self.allocate_register()
self.emit(f" mov {left_reg}, {saved_reg}")
self.free_register(left_reg)
right_reg = self.generate_expression(expr.right)
result_reg = self.allocate_register()
if expr.op == "+":
self.emit(f" add {left_reg}, {right_reg}, {result_reg}")
elif expr.op == "-":
self.emit(f" sub {left_reg}, {right_reg}, {result_reg}")
elif expr.op == "*":
# Simple multiplication using loop
temp_label = self.new_label("mult")
end_label = self.new_label("mult_end")
self.emit(f" lli 0, {result_reg}")
self.emit(f"{temp_label}:")
self.emit(f" cmp {right_reg}, zero")
self.emit(f" jeq {end_label}")
self.emit(f" add {result_reg}, {left_reg}, {result_reg}")
self.emit(f" dec {right_reg}")
self.emit(f" jmp {temp_label}")
self.emit(f"{end_label}:")
elif expr.op == "/":
# Simple division using loop
temp_label = self.new_label("div")
end_label = self.new_label("div_end")
self.emit(f" lli 0, {result_reg}")
self.emit(f"{temp_label}:")
self.emit(f" cmp {left_reg}, {right_reg}")
self.emit(f" jlt {end_label}")
self.emit(f" sub {left_reg}, {right_reg}, {left_reg}")
self.emit(f" inc {result_reg}")
self.emit(f" jmp {temp_label}")
self.emit(f"{end_label}:")
elif expr.op in ["==", "!=", "<", ">", "<=", ">="]:
self.emit(f" cmp {left_reg}, {right_reg}")
# Result is 1 if condition true, 0 otherwise
self.emit(f" lli 0, {result_reg}")
true_label = self.new_label("cmp_true")
end_label = self.new_label("cmp_end")
if expr.op == "==":
self.emit(f" jeq {true_label}")
elif expr.op == "!=":
self.emit(f" jne {true_label}")
elif expr.op == "<":
self.emit(f" jlt {true_label}")
elif expr.op == ">":
self.emit(f" jgt {true_label}")
elif expr.op == "<=":
self.emit(f" jle {true_label}")
elif expr.op == ">=":
self.emit(f" jge {true_label}")
self.emit(f" jmp {end_label}")
self.emit(f"{true_label}:")
self.emit(f" lli 1, {result_reg}")
self.emit(f"{end_label}:")
self.free_register(left_reg)
self.free_register(right_reg)
return result_reg
def compile_c_to_asm(source: str) -> str:
"""Compile C source code to DSA assembly."""
lexer = Lexer(source)
tokens = lexer.tokenize()
parser = Parser(tokens)
ast = parser.parse()
codegen = CodeGenerator()
assembly = codegen.generate(ast)
return assembly
def main():
if len(sys.argv) < 2:
print("Usage: python compiler.py <input.c> [output.dsa]")
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2] if len(sys.argv) > 2 else input_file.replace(".c", ".dsa")
with open(input_file, "r") as f:
source = f.read()
try:
assembly = compile_c_to_asm(source)
with open(output_file, "w") as f:
f.write(assembly)
print(f"Successfully compiled {input_file} to {output_file}")
except (SyntaxError, RuntimeError) as e:
print(f"Compilation error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
# # Example usage
# if len(sys.argv) > 1:
# example_c = sys.argv[1]
# else:
# example_c = """
# int factorial(int n) {
# if (n <= 1) {
# return 1;
# }
# return n * factorial(n - 1);
# }
# int main() {
# int result;
# result = factorial(5);
# return result;
# }
# """
# print("Example C program:")
# print(example_c)
# print("\n" + "="*60 + "\n")
# print("Generated DSA assembly:")
# print(compile_c_to_asm(example_c))
-12
View File
@@ -1,12 +0,0 @@
int factorial(int n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
int main() {
int res = factorial(3);
printnum(res);
return 0;
}
-25
View File
@@ -1,25 +0,0 @@
include print: "lib/io/print.dsa"
int factorial(int n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
int add_(int a, int b) {
return a + b;
}
int greater(int a, int b) {
if (a + a > b + b) {
return a;
} else {
return b + a;
}
}
int main() {
printnum(-5);
return 0;
}
-5
View File
@@ -1,5 +0,0 @@
// Imports
include maths: "./lib/maths/core.dsa"
// Reserved Memory
-106
View File
@@ -1,106 +0,0 @@
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[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,
NoReg,
// system registers - can't be written to by instructions.
Mar,
Mdr,
Sts,
Cir,
Pcx,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
#[non_exhaustive]
/// A list of all current instructions in the DSA Assembly language.
pub enum Instruction {
// No-op
Nop = 0x0,
// Data transfer instructions
Mov(Register, Register) = 0x1,
Movs(Register, Register) = 0x2,
Ldb(Register, Register, Option<u32>) = 0x3,
Ldbs(Register, Register, Option<u32>) = 0x4,
Ldh(Register, Register, Option<u32>) = 0x5,
Ldhs(Register, Register, Option<u32>) = 0x6,
Ldw(Register, Register, Option<u32>) = 0x7,
Stb(Register, Register, Option<u32>) = 0x8,
Sth(Register, Register, Option<u32>) = 0x9,
Stw(Register, Register, Option<u32>) = 0xA,
Lli(u16, Register) = 0xB,
Lui(u16, Register) = 0xC,
// Jump Instructions
Jump(u16, Register) = 0xD,
JumpEq(u16, Register) = 0xE,
JumpNeq(u16, Register) = 0xF,
JumpGt(u16, Register) = 0x10,
JumpGe(u16, Register) = 0x11,
JumpLt(u16, Register) = 0x12,
JumpLe(u16, Register) = 0x13,
// Comparison
Compare(Register, Register) = 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,
}
-599
View File
@@ -1,599 +0,0 @@
use std::collections::HashMap;
use std::hash::Hash;
use std::sync::LazyLock;
use std::sync::atomic::AtomicU32;
use std::time::SystemTime;
use chrono::{DateTime, Local};
use crate::registers::RegisterAllocator;
use crate::{block, cmd, comment, dsa};
use crate::parser::{
BinaryOperator, ConstExpr, Declaration, Expression, Parameter, Program, Statement,
UnaryOperator,
};
pub struct CodeGenerator {
ast: Program,
imports: HashMap<String, String>,
globals: Vec<String>,
functions: Vec<String>,
symbols: Vec<String>,
allocator: RegisterAllocator,
}
static GLOBAL_METHODS: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
HashMap::from([("print", "print::print"), ("printnum", "print::print_num")])
});
fn import(name: &str, path: &str) -> String {
format!("include {name}: \"{}\"", path)
}
impl CodeGenerator {
const RET: &'static str = "\tjmp _ret";
pub fn new(ast: Program) -> Self {
CodeGenerator {
ast,
imports: HashMap::new(),
globals: Vec::new(),
functions: Vec::new(),
symbols: Vec::new(),
allocator: RegisterAllocator::new(),
}
}
pub fn include(&mut self, name: &str, path: &str) {
self.imports.insert(name.to_string(), path.to_string());
}
pub fn generate(&mut self) -> Result<String, String> {
// 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 { name, .. } => self.symbols.push(name),
Declaration::Function { name, .. } => self.symbols.push(name),
Declaration::Import { name, .. } => self.symbols.push(name),
}
}
for block in self.ast.clone().declarations {
self.generate_block(block.clone())?;
}
self.generate_layout()
}
fn generate_layout(&mut self) -> Result<String, String> {
let datetime: DateTime<Local> = SystemTime::now().into();
Ok(dsa![
"",
comment!("GENERATED BY DSA-C COMPILER"),
comment!(format!(
"Generated at {}",
datetime.format("%Y-%m-%d %H:%M:%S")
)),
"",
// imports
comment!("Imports"),
self.imports
.iter()
.map(|(k, v)| import(k, v))
.collect::<Vec<String>>()
.join("\n"),
"",
// reserved memory
comment!("Globals & Reserved Memory"),
self.globals.join("\n"),
"",
// entry point
comment!("Entry Point"),
"dw stack: 0x10000",
"db message: \"Process Exited with code:\"",
block! [ "_init"
dsa![ldw stack, bpr],
dsa![mov bpr, spr],
dsa![push zero],
dsa![call main],
dsa![call print::print_newline],
dsa![lwi message, rg0],
dsa![push rg0],
dsa![call print::print],
dsa![pop zero],
dsa![call print::print_hex_word],
dsa![pop zero],
dsa![hlt]
],
"",
comment!("Function return boilerplate"),
block! [ "_ret"
dsa![mov bpr, spr],
dsa![pop bpr],
dsa![return]
],
// block! [ "main"
// dsa![push bpr],
// dsa![mov spr, bpr],
// dsa![lwi 67, rg1],
// dsa![stw rg1, spr, 8],
// dsa![mov bpr, spr],
// dsa![pop bpr],
// dsa![return]
// ],
"",
self.functions.join("\n"),
])
}
fn generate_global(&mut self, name: &str, init: Option<ConstExpr>) {
self.globals.push(format!(
"dw {}: {}",
name,
init.unwrap_or(ConstExpr::Number(0))
))
}
fn generate_block(&mut self, block: Declaration) -> Result<(), String> {
match block {
Declaration::Variable { name, init } => self.generate_global(&name, init),
Declaration::Function {
name,
return_type,
params,
body,
} => {
let func = self.generate_function(&name, &params, &body).join("\n");
self.functions.push(format!("{func}\n"));
}
Declaration::Import { name, path } => {
self.imports.insert(name, path);
}
};
Ok(())
}
// Example: Generate code for a function
fn generate_function(
&mut self,
name: &str,
params: &[Parameter],
body: &[Statement],
) -> Vec<String> {
let mut code = Vec::new();
// Reset allocator for new function
self.allocator.reset();
// Function prologue
code.push(format!("{}:", name));
code.push("\tpush bpr".to_string());
code.push("\tmov spr, bpr".to_string());
code.push(String::new());
// 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(&param.name).unwrap();
code.extend(load_code);
code.push(format!("\tldw bpr, {}, {}", reg, offset));
}
// Generate code for function body
for stmt in body {
let stmt_code = self.generate_statement(stmt).unwrap();
code.extend(stmt_code);
}
// automatically return at function end
if let Some(x) = code.last()
&& x == Self::RET
{
} else {
code.push(Self::RET.to_string());
}
code
}
// Example: Generate code for a statement
fn generate_statement(&mut self, stmt: &Statement) -> Result<Vec<String>, String> {
let mut code = Vec::new();
match stmt {
Statement::Assign {
name,
declare_type,
value,
} => {
if let Some(expr) = value {
// Evaluate expression
let (result_reg, expr_code) = self.generate_expression(expr)?;
code.extend(expr_code);
// Store result in variable
let store_code = self.allocator.store_var(name, &result_reg);
code.extend(store_code);
// Free temporary register
self.allocator.free_temp(&result_reg);
} else {
// Just declaring variable without initialization
self.allocator.alloc_var(name)?;
}
}
Statement::Return { expr } => {
if let Some(e) = expr {
let (result_reg, expr_code) = self.generate_expression(e)?;
code.extend(expr_code);
code.push(format!("\tstw {}, bpr, 8", result_reg));
code.push(format!("\tjmp _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)?;
code.extend(cond_code);
// Compare with zero
code.push(format!("\tcmp {}, zero", cond_reg));
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(format!("\tjeq {}", else_label));
// Then block
code.push(format!("{}:", then_label));
for s in then_stmt {
code.extend(self.generate_statement(s)?);
}
if then_stmt.len() == 0 {
code.push("\tnop".to_string());
}
code.push(format!("\tjmp {}", end_label));
// Else block
code.push(format!("{}:", else_label));
for s in else_stmt {
code.extend(self.generate_statement(s)?);
}
if else_stmt.len() == 0 {
code.push("\tnop".to_string());
}
code.push(format!("{}:", 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(format!("{}:", loop_start));
// Generate condition
let (cond_reg, cond_code) = self.generate_expression(condition)?;
code.extend(cond_code);
code.push(format!("\tcmp {}, zero", cond_reg));
self.allocator.free_temp(&cond_reg);
code.push(format!("\tjeq {}", loop_end));
// Loop body
for s in body {
code.extend(self.generate_statement(s)?);
}
code.push(format!("\tjmp {}", loop_start));
code.push(format!("{}:", loop_end));
}
Statement::Expression { expr } => {
let (result_reg, expr_code) = self.generate_expression(expr)?;
code.extend(expr_code);
self.allocator.free_temp(&result_reg);
}
Statement::Block(statements) => {
for s in statements {
code.extend(self.generate_statement(s)?);
}
}
}
Ok(code)
}
// Example: Generate code for an expression
// Returns (register containing result, assembly code)
fn generate_expression(
&mut self,
expr: &Expression,
) -> Result<(String, Vec<String>), String> {
let mut code = Vec::new();
match expr {
Expression::Number { value } => {
let (reg, alloc_code) = self.allocator.alloc_temp()?;
code.extend(alloc_code);
// Load immediate value
code.push(format!("\tlli {}, {}", value & 0xFFFF, reg));
if *value > 0xFFFF || *value < 0 {
code.push(format!("\tlui {}, {}", (value >> 16) & 0xFFFF, reg));
}
Ok((reg, code))
}
Expression::Variable { name, .. } => {
let (reg, load_code) = self.allocator.load_var(name)?;
code.extend(load_code);
Ok((reg, code))
}
Expression::Binary { op, left, right } => {
// Evaluate left operand
let (left_reg, left_code) = self.generate_expression(left)?;
code.extend(left_code);
// Evaluate right operand
let (right_reg, right_code) = self.generate_expression(right)?;
code.extend(right_code);
// Allocate result register
let (result_reg, result_alloc) = self.allocator.alloc_temp()?;
code.extend(result_alloc);
// Generate operation
match op {
BinaryOperator::Add => {
code.push(format!(
"\tadd {}, {}, {}",
left_reg, right_reg, result_reg
));
}
BinaryOperator::Sub => {
code.push(format!(
"\tsub {}, {}, {}",
left_reg, right_reg, result_reg
));
}
BinaryOperator::Mul => {
self.include("maths", "./lib/maths/core.dsa");
// Call multiply function
code.push(format!("\tpush {}", right_reg));
code.push(format!("\tpush {}", left_reg));
code.push("\tcall maths::multiply".to_string());
code.push(format!("\tpop {}", result_reg));
code.push("\tpop zero".to_string());
}
// Comparison operators - return 1 (true) or 0 (false)
BinaryOperator::Eq => {
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
code.push(format!("\tlli 0, {}", result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(format!("\tjne {}", end_label)); // If not equal, skip setting to 1
code.push(format!("\tlli 1, {}", result_reg));
code.push(format!("{}:", end_label));
}
BinaryOperator::Ne => {
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
code.push(format!("\tlli 0, {}", result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(format!("\tjeq {}", end_label)); // If equal, skip setting to 1
code.push(format!("\tlli 1, {}", result_reg));
code.push(format!("{}:", end_label));
}
BinaryOperator::Lt => {
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
code.push(format!("\tlli 0, {}", result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(format!("\tjge {}", end_label)); // If greater or equal, skip setting to 1
code.push(format!("\tlli 1, {}", result_reg));
code.push(format!("{}:", end_label));
}
BinaryOperator::Le => {
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
code.push(format!("\tlli 0, {}", result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(format!("\tjgt {}", end_label)); // If greater than, skip setting to 1
code.push(format!("\tlli 1, {}", result_reg));
code.push(format!("{}:", end_label));
}
BinaryOperator::Gt => {
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
code.push(format!("\tlli 0, {}", result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(format!("\tjle {}", end_label)); // If less or equal, skip setting to 1
code.push(format!("\tlli 1, {}", result_reg));
code.push(format!("{}:", end_label));
}
BinaryOperator::Ge => {
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
code.push(format!("\tlli 0, {}", result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(format!("\tjlt {}", end_label)); // If less than, skip setting to 1
code.push(format!("\tlli 1, {}", result_reg));
code.push(format!("{}:", end_label));
}
_ => return Err(format!("Unsupported binary operator: {:?}", op)),
}
// Free operand registers (allocator will protect variables)
self.allocator.free_temp(&left_reg);
self.allocator.free_temp(&right_reg);
Ok((result_reg, code))
}
Expression::Call { name, args } => {
// Save caller-saved registers and track which ones we saved
let saved_regs = self.allocator.get_caller_saved_registers();
for reg in &saved_regs {
code.push(format!("\tpush {}", reg));
}
// Evaluate and push arguments in reverse order
let mut arg_regs = Vec::new();
for arg in args.iter().rev() {
let (arg_reg, arg_code) = self.generate_expression(arg)?;
code.extend(arg_code);
code.push(format!("\tpush {}", arg_reg));
arg_regs.push(arg_reg);
}
if GLOBAL_METHODS.contains_key(name.as_str()) {
code.push(format!("\tcall {}", GLOBAL_METHODS[name.as_str()]));
} else if self.symbols.contains(name) {
// Call local function
code.push(format!("\tcall {}", name));
} else {
return Err(format!("undefined function {name}"));
}
// Result is in rg0, allocate a register and move it
let (result_reg, result_alloc) = self.allocator.alloc_temp()?;
code.extend(result_alloc);
code.push(format!("\tpop {}", result_reg));
// Clean up arguments
if args.len() > 1 {
for _ in 0..(args.len() - 1) {
code.push("\tpop zero".to_string());
}
}
// Restore caller-saved registers in reverse order (LIFO)
for reg in saved_regs.iter().rev() {
code.push(format!("\tpop {}", reg));
}
// Free argument registers
for reg in arg_regs {
self.allocator.free_temp(&reg);
}
Ok((result_reg, code))
}
Expression::Unary { op, operand } => {
let (operand_reg, operand_code) = self.generate_expression(operand)?;
code.extend(operand_code);
let (result_reg, result_alloc) = self.allocator.alloc_temp()?;
code.extend(result_alloc);
match op {
UnaryOperator::Minus => {
// Negate: result = 0 - operand
code.push(format!("\tsub zero, {}, {}", operand_reg, result_reg));
}
UnaryOperator::Plus => {
// Just move
code.push(format!("\tmov {}, {}", operand_reg, result_reg));
}
}
self.allocator.free_temp(&operand_reg);
Ok((result_reg, code))
}
Expression::Empty => Ok(("zero".to_string(), 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()
}
}
/// Build a single string from any number of arguments.
/// Each argument must implement `Display` or be convertible to a string.
#[macro_export]
macro_rules! dsa {
($($arg:expr),* $(,)?) => {{
// Start with an empty String well grow it as we go.
use std::fmt::Write;
let mut s = ::std::string::String::new();
$(
// `write!` is cheaper than `format!` for each element
// because it reuses the same buffer.
write!(s, "{}\n", $arg).expect("write to String failed");
)*
s
}};
}
// ──────────────────────── dsa! ────────────────────────
// A tiny helper that just turns its tokenstream into a string.
// The trailing comma is kept its part of the syntax you want.
#[macro_export]
macro_rules! cmd {
($($tokens:tt)*) => {{
// Well just stringify the tokens and return a String.
format!("{}", concat!(stringify!($tokens), "\n"))
}};
}
// ──────────────────────── block! ────────────────────────
// Usage:
//
// let asm = block![ "name"
// dsa![mov rg0, rg1],
// dsa![add rg1, rg1]
// ];
//
// `asm` is a `&'static str` containing:
//
// name:
// mov rg0, rg1
// add rg1, rg1
//
#[macro_export]
macro_rules! block {
// The first token must be a string literal thats the label.
($label:literal $(dsa![$($ins:tt)*]),* ) => {{
// Build a single string at compile time.
const CODE: &str = concat!(
$label, ":\n",
// Each `dsa!` call yields a string like `"mov rg0, rg1"`.
// We add a newline after each one to get the desired layout.
$(concat!("\t", stringify!($($ins)*), "\n")),*
);
CODE
}};
}
#[macro_export]
macro_rules! comment {
($text:expr) => {{ format!("// {}", $text) }};
}
-335
View File
@@ -1,335 +0,0 @@
// ============================================================================
// 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,
}
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)
}
}
-74
View File
@@ -1,74 +0,0 @@
use std::fmt;
use crate::{codegen::CodeGenerator, lexer::Lexer, parser::Parser};
// mod assembly;
pub mod codegen;
pub mod lexer;
pub mod parser;
mod registers;
// ============================================================================
// Main & Tests
// ============================================================================
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.c> [output.dsa]");
return;
}
let input_file = &args[1];
let output_file = if args.len() > 2 {
&args[2]
} else {
"output.dsa"
};
// read input
let input = std::fs::read_to_string(input_file).expect("Failed to read input file");
// Lexing
let mut lexer = Lexer::new(&input);
let tokens = match lexer.tokenize() {
Ok(tokens) => tokens,
Err(e) => {
eprintln!("Lexing error: {}", e);
return;
}
};
println!("Tokens:");
for token in &tokens {
println!(" {:?}", token.token_type);
}
println!();
// Parsing
let mut parser = Parser::new(tokens);
let ast = match parser.parse() {
Ok(ast) => ast,
Err(e) => {
eprintln!("Parsing error: {}", e);
return;
}
};
println!("AST:");
println!("{:#?}", ast);
// Code Gen
let mut generator = CodeGenerator::new(ast);
let result = match generator.generate() {
Ok(code) => code,
Err(e) => {
eprintln!("Parsing error: {}", e);
return;
}
};
std::fs::write(output_file, &result).expect("Failed to write output");
println!("Result written to {}", output_file);
}
-610
View File
@@ -1,610 +0,0 @@
// ============================================================================
// AST Node Types
// ============================================================================
use std::fmt;
use crate::lexer::{Token, TokenType};
#[derive(Debug, Clone)]
pub struct Program {
pub declarations: Vec<Declaration>,
}
#[derive(Debug, Clone)]
pub enum Declaration {
Function {
name: String,
return_type: Type,
params: Vec<Parameter>,
body: Block,
},
Variable {
name: String,
init: Option<ConstExpr>,
},
Import {
name: String,
path: String,
},
}
#[derive(Debug, Clone)]
pub struct Parameter {
pub name: String,
pub param_type: Type,
}
#[derive(Debug, Clone)]
pub enum Type {
Int,
Long,
Float,
Double,
Char,
Void,
Ptr(Box<Type>),
Array(Box<Type>, usize),
Struct(String),
}
pub type Block = Vec<Statement>;
#[derive(Debug, Clone)]
pub enum Statement {
Block(Block),
Assign {
// left side
name: String,
declare_type: Option<Type>,
// right side
value: Option<Box<Expression>>,
},
Expression {
expr: Expression,
},
If {
condition: Expression,
then_stmt: Block,
else_stmt: Block,
},
While {
condition: Expression,
body: Vec<Statement>,
},
Return {
expr: 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),
}
}
}
#[derive(Debug, Clone)]
pub enum Expression {
Empty,
Binary {
op: BinaryOperator,
left: Box<Expression>,
right: Box<Expression>,
},
Unary {
op: UnaryOperator,
operand: Box<Expression>,
},
Variable {
name: String,
expr_type: Option<Type>,
},
Number {
value: i32,
},
Call {
name: String,
args: Vec<Expression>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum BinaryOperator {
Add,
Sub,
Mul,
Div,
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
}
impl fmt::Display for BinaryOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
BinaryOperator::Add => write!(f, "+"),
BinaryOperator::Sub => write!(f, "-"),
BinaryOperator::Mul => write!(f, "*"),
BinaryOperator::Div => write!(f, "/"),
BinaryOperator::Eq => write!(f, "=="),
BinaryOperator::Ne => write!(f, "!="),
BinaryOperator::Lt => write!(f, "<"),
BinaryOperator::Gt => write!(f, ">"),
BinaryOperator::Le => write!(f, "<="),
BinaryOperator::Ge => write!(f, ">="),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum UnaryOperator {
Plus,
Minus,
}
impl fmt::Display for UnaryOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
UnaryOperator::Plus => write!(f, "+"),
UnaryOperator::Minus => write!(f, "-"),
}
}
}
// ============================================================================
// 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::Import { 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::<Parameter>::new();
if !matches!(self.current().token_type, TokenType::RParen) {
self.expect(TokenType::Int)?;
match &self.current().token_type {
TokenType::Identifier(s) => {
params.push(Parameter {
name: s.clone(),
param_type: Type::Int,
});
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(Parameter {
name: s.clone(),
param_type: Type::Int,
});
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: Type::Int,
})
}
_ => {
// 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 { name, init })
}
}
}
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 {
name,
value: Some(Box::new(expr)),
declare_type: None,
})
}
// var expression
else {
self.expect(TokenType::Semicolon)?;
Ok(Statement::Expression {
expr: Expression::Variable {
name,
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 {
name,
value: Some(Box::new(init_expr)),
declare_type: Some(Type::Int),
}
} else {
Statement::Assign {
name,
value: None,
declare_type: Some(Type::Int),
}
};
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,
};
}
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,
};
}
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,
};
}
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 });
}
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 })
}
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, args })
} else {
Ok(Expression::Variable {
name,
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
))),
}
}
}
-344
View File
@@ -1,344 +0,0 @@
use std::collections::HashMap;
/// Register allocator for DSA assembly generation
/// Manages general-purpose registers (rg0-rgf) and handles stack spilling
pub struct RegisterAllocator {
/// Available general-purpose registers
available_registers: Vec<String>,
/// 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<String, 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: HashMap<String, bool>,
}
#[derive(Debug, Clone)]
pub enum Location {
Register(String),
Stack(i32), // offset from bpr
}
impl RegisterAllocator {
pub fn new() -> Self {
// Initialize with available GP registers (rg0-rgf = 16 registers)
let registers = vec![
"rg0", "rg1", "rg2", "rg3", "rg4", "rg5", "rg6", "rg7", "rg8", "rg9", "rga",
"rgb", "rgc", "rgd", "rge", "rgf",
]
.into_iter()
.map(String::from)
.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: HashMap::new(),
}
}
/// 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<(String, Vec<String>), String> {
let mut code = Vec::new();
// Try to find an unused register
for reg in &self.available_registers {
if !self.in_use.get(reg).unwrap_or(&false) {
self.in_use.insert(reg.clone(), true);
return Ok((reg.clone(), code));
}
}
// 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(&reg)?;
code.extend(spill_code);
self.in_use.insert(reg.clone(), true);
return Ok((reg, code));
}
Err("No registers available and nothing to spill".to_string())
}
/// 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: &str) {
// Check if this register contains a variable
if self.register_contents.contains_key(reg) {
// 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
self.in_use.insert(reg.to_string(), false);
}
/// 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<(String, Vec<String>), String> {
// Check if variable already has a location
if let Some(location) = self.variable_locations.get(var_name).cloned() {
match location {
Location::Register(reg) => {
return Ok((reg.clone(), Vec::new()));
}
Location::Stack(offset) => {
// Variable is on stack, load it into a register
let (reg, mut code) = self.alloc_temp()?;
code.push(format!("\tldw bpr, {}, {}", reg, offset));
// Update location to register
self.variable_locations
.insert(var_name.to_string(), Location::Register(reg.clone()));
self.register_contents
.insert(reg.clone(), 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.clone()));
self.register_contents
.insert(reg.clone(), 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<(String, Vec<String>), String> {
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: &str) -> Vec<String> {
let mut code = Vec::new();
// Check if variable already has a location
if let Some(location) = self.variable_locations.get(var_name) {
match location {
Location::Register(dest_reg) => {
if dest_reg != source_reg {
code.push(format!("\tmov {}, {}", source_reg, dest_reg));
}
}
Location::Stack(offset) => {
code.push(format!("\tstw {}, bpr, {}", source_reg, offset));
}
}
} else {
// Variable doesn't exist yet - try to allocate a register
if let Some(free_reg) = self.find_free_register() {
if &free_reg != source_reg {
code.push(format!("\tmov {}, {}", source_reg, free_reg));
}
self.variable_locations
.insert(var_name.to_string(), Location::Register(free_reg.clone()));
self.register_contents
.insert(free_reg.clone(), var_name.to_string());
self.in_use.insert(free_reg, true);
} else {
// 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
}
}
code
}
/// Spill a register to the stack
/// Returns assembly code to perform the spill
fn spill_register(&mut self, reg: &str) -> Result<Vec<String>, String> {
let mut code = Vec::new();
if let Some(var_name) = self.register_contents.get(reg).cloned() {
// Store register content to stack
code.push(format!("\tstw {}, bpr, {}", reg, self.stack_offset));
// Update variable location
self.variable_locations
.insert(var_name.clone(), Location::Stack(self.stack_offset));
// Remove from register tracking
self.register_contents.remove(reg);
// Move to next stack slot
self.stack_offset -= 4;
}
Ok(code)
}
/// Find a free register (not currently in use)
fn find_free_register(&self) -> Option<String> {
for reg in &self.available_registers {
if !self.in_use.get(reg).unwrap_or(&false) {
return Some(reg.clone());
}
}
None
}
/// Spill all registers to stack (useful before function calls)
pub fn spill_all(&mut self) -> Vec<String> {
let mut code = Vec::new();
let regs_to_spill: Vec<String> = self.register_contents.keys().cloned().collect();
for reg in regs_to_spill {
if let Ok(spill_code) = self.spill_register(&reg) {
code.extend(spill_code);
}
}
code
}
/// 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.clear();
}
/// Mark a variable as dead (no longer needed)
/// Frees its register if it's in one
pub fn free_var(&mut self, var_name: &str) {
if let Some(Location::Register(reg)) = self.variable_locations.get(var_name) {
let reg = reg.clone();
self.register_contents.remove(&reg);
self.in_use.insert(reg, false);
}
self.variable_locations.remove(var_name);
}
/// 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<String> {
self.register_contents
.iter()
.filter(|(reg, _)| *self.in_use.get(*reg).unwrap_or(&false))
.map(|(reg, _)| reg.clone())
.collect()
}
/// Save caller-saved registers before a function call
/// Returns assembly code to save them
pub fn save_caller_saved(&mut self) -> Vec<String> {
let mut code = Vec::new();
// For simplicity, save all currently used registers
// In a more sophisticated compiler, you'd only save registers that are live
for (reg, var_name) in self.register_contents.clone() {
if *self.in_use.get(&reg).unwrap_or(&false) {
code.push(format!("\tpush {}", reg));
}
}
code
}
/// Restore caller-saved registers after a function call
/// Returns assembly code to restore them
pub fn restore_caller_saved(&mut self, saved_regs: &[String]) -> Vec<String> {
let mut code = Vec::new();
// Restore in reverse order (LIFO)
for reg in saved_regs.iter().rev() {
code.push(format!("\tpop {}", reg));
}
code
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_allocation() {
let mut allocator = RegisterAllocator::new();
let (reg1, code1) = allocator.alloc_temp().unwrap();
assert_eq!(code1.len(), 0); // No spill needed
assert_eq!(reg1, "rg0");
let (reg2, code2) = allocator.alloc_temp().unwrap();
assert_eq!(code2.len(), 0);
assert_eq!(reg2, "rg1");
allocator.free_temp(&reg1);
let (reg3, code3) = allocator.alloc_temp().unwrap();
assert_eq!(code3.len(), 0);
assert_eq!(reg3, "rg0"); // Reuses freed register
}
#[test]
fn test_variable_allocation() {
let mut allocator = RegisterAllocator::new();
let (reg, _) = allocator.alloc_var("x").unwrap();
assert_eq!(reg, "rg0");
// Requesting same variable again should return same register
let (reg2, _) = allocator.alloc_var("x").unwrap();
assert_eq!(reg2, "rg0");
}
#[test]
fn test_stack_allocation() {
let mut allocator = RegisterAllocator::new();
// Allocate all 16 registers
for i in 0..16 {
allocator.alloc_var(&format!("var{}", i)).unwrap();
}
// Next allocation should spill to stack
let (reg, code) = allocator.alloc_var("var16").unwrap();
assert!(code.len() > 0); // Should have spill code
}
}
+1
View File
@@ -5,3 +5,4 @@ edition.workspace = true
authors.workspace = true
[dependencies]
object = { version = "0.37.1", default-features = false, features = ["elf", "std", "read", "read_core", "write_std", "write", "alloc", "build"] }
+3
View File
@@ -0,0 +1,3 @@
# Common types and methods for the DSA
This library contains the instruction set, encoding and decoding routines, and ELF encoding and loading routines (WIP).
+8
View File
@@ -0,0 +1,8 @@
//! ELF file creation and parsing routines.
use object::{Endianness, build::elf::Builder};
#[allow(clippy::missing_const_for_fn)]
pub fn write() {
let _builder = Builder::new(Endianness::Little, false);
}
+4 -5
View File
@@ -1,18 +1,17 @@
use crate::{instructions::encode::Encode, prelude::*};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
pub enum Interrupt {
Software(u8),
Breakpoint,
#[default]
HardFault,
}
pub type Address = u32;
impl Interrupt {
// someone tell clippy to stfu.
#[allow(clippy::must_use_candidate)]
pub const fn as_u8(self) -> u8 {
const fn as_u8(self) -> u8 {
match self {
Self::Breakpoint => 0,
Self::HardFault => 1,
@@ -40,7 +39,7 @@ pub enum InstructionType {
Immediate,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Register {
// general purpose registers
+14 -9
View File
@@ -1,4 +1,5 @@
//! Various types of arguments that instructions can take, alongside encoding and decoding logic.
//! Various types of arguments that instructions can take, alongside encoding and decoding
//! logic.
use crate::{
instructions::{RegisterParseError, encode::Encode},
@@ -35,18 +36,20 @@ impl std::fmt::Display for ArgsDecodeError {
impl std::error::Error for ArgsDecodeError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
/// 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.
/// 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`].
/// 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();
@@ -56,8 +59,8 @@ impl ITypeArgs {
}
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.
/// 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;
@@ -84,7 +87,7 @@ impl TryFrom<u32> for ITypeArgs {
}
/// Used by instructions not using immediates (besides 5 bit shift values).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct RTypeArgs {
pub sr1: Register,
pub sr2: Register,
@@ -95,7 +98,8 @@ pub struct RTypeArgs {
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.
/// 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>,
@@ -122,7 +126,8 @@ impl Encode for RTypeArgs {
///
/// # Arguments
///
/// - `shamt`: The amount to shift value (used only in shift instructions, otherwise 0).
/// - `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;
+3 -5
View File
@@ -54,14 +54,12 @@ impl Encode for Instruction {
],
no_args: [Nop, IntReturn, Halt],
special: [
Self::Interrupt(_) => todo!(),
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)
let segment = segment as u8;
(opcode << 26) | u32::from(segment)
}
]
)
+3 -1
View File
@@ -39,7 +39,9 @@ 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}")?,
Self::InvalidArgument(err) => {
write!(f, "invalid arguments, got an error {err}")?;
}
}
Ok(())
+1 -1
View File
@@ -12,8 +12,8 @@
clippy::match_wildcard_for_single_variants
)]
pub mod elf;
pub mod instructions;
pub mod logging;
pub mod prelude {
//! A collection of types you should definitely import when working with this crate.
-4
View File
@@ -1,4 +0,0 @@
// TODO: Use an actual logging or tracing library for pretty (scoped) output.
pub fn log(message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}");
}
-9
View File
@@ -1,9 +0,0 @@
[package]
name = "compiler"
version.workspace = true
edition.workspace = true
authors.workspace = true
[dependencies]
chrono = "0.4.43"
common = { path = "../common" }
-129
View File
@@ -1,129 +0,0 @@
# 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
-756
View File
@@ -1,756 +0,0 @@
use std::collections::HashMap;
use std::hash::Hash;
use std::sync::LazyLock;
use std::sync::atomic::AtomicU32;
use std::time::SystemTime;
use chrono::{DateTime, Local};
use crate::registers::{Location, RegisterAllocator};
use crate::{block, cmd, comment, dsa};
use crate::parser::{
BinaryOperator, CompilerError, ConstExpr, Declaration, Dependency, Expression,
Program, Statement, UnaryOperator, Variable,
};
pub struct CodeGenerator {
ast: Program,
imports: HashMap<String, String>,
globals: Vec<String>,
functions: Vec<String>,
symbols: Vec<String>,
allocator: RegisterAllocator,
}
static GLOBAL_METHODS: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
HashMap::from([
// ("print", "print::print"),
// ("println", "print::println"),
// ("printnum", "print::print_num"),
// ("print_space", "print::print_whitespace"),
// ("print_newline", "print::print_newline"),
// ("print_char", "print::print_byte"),
// ("print_word", "print::print_word"),
// ("print_hex", "print::print_hex_word"),
])
});
fn import(name: &str, path: &str) -> String {
format!("include {name}: \"{}\"", path)
}
impl CodeGenerator {
const RET: &'static str = "\tjmp _ret";
pub fn new(ast: Program) -> Self {
CodeGenerator {
ast,
imports: HashMap::new(),
globals: Vec::new(),
functions: Vec::new(),
symbols: Vec::new(),
allocator: RegisterAllocator::new(),
}
}
pub fn include(&mut self, name: &str, path: &str) {
self.imports.insert(name.to_string(), path.to_string());
}
fn is_global(&self, name: &str) -> bool {
// Check if this variable is in the globals list
self.globals
.iter()
.any(|g| g.contains(&format!("dw {}:", 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)
}
}
}
for block in self.ast.clone().declarations {
self.generate_block(block.clone())?;
}
self.generate_layout()
}
fn generate_layout(&mut self) -> Result<String, CompilerError> {
let datetime: DateTime<Local> = SystemTime::now().into();
Ok(dsa![
"",
comment!("GENERATED BY DSC COMPILER"),
comment!(format!(
"Generated at {}",
datetime.format("%Y-%m-%d %H:%M:%S")
)),
"",
// imports
comment!("Imports"),
self.imports
.iter()
.map(|(k, v)| import(k, v))
.collect::<Vec<String>>()
.join("\n"),
"",
// reserved memory
comment!("Globals & Reserved Memory"),
self.globals.join("\n"),
"",
// entry point
comment!("Entry Point"),
"dw stack: 0x10000",
"db message: \"Process Exited with code:\"",
block! [ "_init"
dsa![ldw stack, bpr],
dsa![mov bpr, spr],
dsa![push zero],
dsa![call main],
dsa![call print::print_newline],
dsa![lwi message, rg0],
dsa![push rg0],
dsa![call print::print],
dsa![pop zero],
dsa![call print::print_hex_word],
dsa![pop zero],
dsa![hlt]
],
"",
comment!("Return"),
block! [ "_ret"
dsa![mov bpr, spr],
dsa![pop bpr],
dsa![return]
],
comment!("Compiled Code Starts..."),
// block! [ "main"
// dsa![push bpr],
// dsa![mov spr, bpr],
// dsa![lwi 67, rg1],
// dsa![stw rg1, spr, 8],
// dsa![mov bpr, spr],
// dsa![pop bpr],
// dsa![return]
// ],
self.functions.join("\n"),
])
}
fn generate_global(&mut self, name: &str, init: Option<ConstExpr>) {
self.globals.push(format!(
"dw {}: {}",
name,
init.unwrap_or(ConstExpr::Number(0))
))
}
fn generate_block(&mut self, block: Declaration) -> Result<(), CompilerError> {
match block {
Declaration::Variable { var, init, .. } => {
self.generate_global(&var.name, init)
}
Declaration::Function {
name,
return_type,
params,
body,
} => {
let func = self.generate_function(&name, &params, &body).join("\n");
self.functions.push(format!("{func}\n"));
}
Declaration::Dependency(Dependency { name, path }) => {
self.imports.insert(name, path);
}
};
Ok(())
}
// Example: Generate code for a function
fn generate_function(
&mut self,
name: &str,
params: &[Variable],
body: &[Statement],
) -> Vec<String> {
let mut code = Vec::new();
// Reset allocator for new function
self.allocator.reset();
// Function prologue
code.push(format!("{}:", name));
code.push("\tpush bpr".to_string());
code.push("\tmov spr, bpr".to_string());
code.push(String::new());
// 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(&param.name).unwrap();
code.extend(load_code);
code.push(format!("\tldw bpr, {}, {}", reg, offset));
}
// Generate code for function body
for stmt in body {
let stmt_code = self.generate_statement(stmt).unwrap();
code.extend(stmt_code);
}
// automatically return at function end
if let Some(x) = code.last()
&& x == Self::RET
{
} else {
code.push(Self::RET.to_string());
}
code
}
// Example: Generate code for a statement
fn generate_statement(
&mut self,
stmt: &Statement,
) -> Result<Vec<String>, CompilerError> {
let mut code = Vec::new();
match stmt {
Statement::Declaration { var, value } => {
if let Some(expr) = value {
// Evaluate expression
let (result_reg, expr_code) = self.generate_expression(expr, true)?;
code.extend(expr_code);
// Store result in variable
let store_code = self.allocator.store_var(&var.name, &result_reg);
code.extend(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!(),
Statement::Continue => unimplemented!(),
Statement::PtrWrite { ptr, value } => {
let (result_reg, expr_code) = self.generate_expression(value, true)?;
code.extend(expr_code);
let (ptr_reg, ptr_code) = self.generate_expression(ptr, true)?;
code.extend(ptr_code);
code.push(format!("\tstw {}, {}", result_reg, ptr_reg));
self.allocator.free_temp(&result_reg);
self.allocator.free_temp(&ptr_reg);
}
Statement::Assign { varname, value } => {
// Evaluate expression
let (result_reg, expr_code) = self.generate_expression(value, true)?;
code.extend(expr_code);
// Check if this is a global variable
if self.is_global(varname) {
// Store to global label
code.push(format!("\tstw {}, {}", result_reg, varname));
} else {
// Store result in local variable
let store_code = self.allocator.store_var(varname, &result_reg);
code.extend(store_code);
}
// Free temporary register
self.allocator.free_temp(&result_reg);
}
Statement::Return(expr) => {
if let Some(e) = expr {
let (result_reg, expr_code) = self.generate_expression(e, true)?;
code.extend(expr_code);
code.push(format!("\tstw {}, bpr, 8", result_reg));
code.push(format!("\tjmp _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)?;
code.extend(cond_code);
// Compare with zero
code.push(format!("\tcmp {}, zero", cond_reg));
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(format!("\tjeq {}", else_label));
// Then block
code.push(format!("{}:", then_label));
for s in then_stmt {
code.extend(self.generate_statement(s)?);
}
if then_stmt.len() == 0 {
code.push("\tnop".to_string());
}
code.push(format!("\tjmp {}", end_label));
// Else block
code.push(format!("{}:", else_label));
for s in else_stmt {
code.extend(self.generate_statement(s)?);
}
if else_stmt.len() == 0 {
code.push("\tnop".to_string());
}
code.push(format!("{}:", 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(format!("{}:", loop_start));
// Generate condition
let (cond_reg, cond_code) = self.generate_expression(condition, true)?;
code.extend(cond_code);
code.push(format!("\tcmp {}, zero", cond_reg));
self.allocator.free_temp(&cond_reg);
code.push(format!("\tjeq {}", loop_end));
// Loop body
for s in body {
code.extend(self.generate_statement(s)?);
}
code.push(format!("\tjmp {}", loop_start));
code.push(format!("{}:", loop_end));
}
Statement::Loop(body) => {
let loop_start = format!("_loop_start_{}", self.get_unique_label());
code.push(format!("{}:", loop_start));
for s in body {
code.extend(self.generate_statement(s)?);
}
code.push(format!("\tjmp {}", loop_start));
}
Statement::Expression { expr } => {
let (result_reg, expr_code) = self.generate_expression(expr, false)?;
code.extend(expr_code);
self.allocator.free_temp(&result_reg);
}
Statement::Block(statements) => {
for s in statements {
code.extend(self.generate_statement(s)?);
}
}
}
Ok(code)
}
// Example: Generate code for an expression
// Returns (register containing result, assembly code)
fn generate_expression(
&mut self,
expr: &Expression,
use_result: bool,
) -> Result<(String, Vec<String>), CompilerError> {
let mut code = Vec::new();
// optimisation to prevent generating dead code!
if expr.is_pure() && !use_result {
return Ok((String::new(), code));
}
match expr {
Expression::StringLiteral(value) => {
let (reg, alloc_code) = self.allocator.alloc_temp()?;
code.extend(alloc_code);
// write string into memory
let uuid = self.get_unique_label();
code.push(format!("\tdb str_{uuid}: \"{value}\""));
// Load pointer to string
code.push(format!("\tlwi str_{uuid}, {reg}"));
Ok((reg, code))
}
Expression::CharLiteral(value) => {
let (reg, alloc_code) = self.allocator.alloc_temp()?;
code.extend(alloc_code);
// Load immediate value
code.push(format!("\tlli {}, {} // '{value}'", *value as u8, reg));
Ok((reg, code))
}
Expression::Number(value) => {
let (reg, alloc_code) = self.allocator.alloc_temp()?;
code.extend(alloc_code);
// Load immediate value
code.push(format!("\tlli {}, {}", value & 0xFFFF, reg));
if *value > 0xFFFF || *value < 0 {
code.push(format!("\tlui {}, {}", (value >> 16) & 0xFFFF, reg));
}
Ok((reg, code))
}
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.extend(alloc_code);
// Load from global label
code.push(format!("\tldw {}, {}", name.name, reg));
Ok((reg, code))
} else {
// Local variable - use existing allocator logic
let (reg, load_code) = self.allocator.load_var(&name.name)?;
code.extend(load_code);
Ok((reg, code))
}
}
Expression::Binary { op, left, right } => {
// Evaluate left operand
let (left_reg, left_code) = self.generate_expression(left, true)?;
code.extend(left_code);
// Evaluate right operand
let (right_reg, right_code) = self.generate_expression(right, true)?;
code.extend(right_code);
// Allocate result register
let (result_reg, result_alloc) = self.allocator.alloc_temp()?;
code.extend(result_alloc);
// Generate operation
match op {
BinaryOperator::Add => {
code.push(format!(
"\tadd {}, {}, {}",
left_reg, right_reg, result_reg
));
}
BinaryOperator::Sub => {
code.push(format!(
"\tsub {}, {}, {}",
left_reg, right_reg, result_reg
));
}
BinaryOperator::Mul => {
self.include("maths", "./lib/maths/core.dsa");
// Call multiply function
code.push(format!("\tpush {}", right_reg));
code.push(format!("\tpush {}", left_reg));
code.push("\tcall maths::multiply".to_string());
code.push(format!("\tpop {}", result_reg));
code.push("\tpop zero".to_string());
}
// Comparison operators - return 1 (true) or 0 (false)
BinaryOperator::Eq => {
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
code.push(format!("\tlli 0, {}", result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(format!("\tjne {}", end_label)); // If not equal, skip setting to 1
code.push(format!("\tlli 1, {}", result_reg));
code.push(format!("{}:", end_label));
}
BinaryOperator::Ne => {
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
code.push(format!("\tlli 0, {}", result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(format!("\tjeq {}", end_label)); // If equal, skip setting to 1
code.push(format!("\tlli 1, {}", result_reg));
code.push(format!("{}:", end_label));
}
BinaryOperator::Lt => {
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
code.push(format!("\tlli 0, {}", result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(format!("\tjge {}", end_label)); // If greater or equal, skip setting to 1
code.push(format!("\tlli 1, {}", result_reg));
code.push(format!("{}:", end_label));
}
BinaryOperator::Le => {
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
code.push(format!("\tlli 0, {}", result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(format!("\tjgt {}", end_label)); // If greater than, skip setting to 1
code.push(format!("\tlli 1, {}", result_reg));
code.push(format!("{}:", end_label));
}
BinaryOperator::Gt => {
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
code.push(format!("\tlli 0, {}", result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(format!("\tjle {}", end_label)); // If less or equal, skip setting to 1
code.push(format!("\tlli 1, {}", result_reg));
code.push(format!("{}:", end_label));
}
BinaryOperator::Ge => {
code.push(format!("\tcmp {}, {}", left_reg, right_reg));
code.push(format!("\tlli 0, {}", result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(format!("\tjlt {}", end_label)); // If less than, skip setting to 1
code.push(format!("\tlli 1, {}", result_reg));
code.push(format!("{}:", 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::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)?;
code.extend(arg_code);
arg_regs.push(arg_reg);
}
// Save caller-saved registers and track which ones we saved
// old method, inefficient.
// let saved_regs = self.allocator.get_caller_saved_registers();
// for reg in &saved_regs {
// code.push(format!("\tpush {}", 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.extend(self.allocator.spill_register(reg).unwrap());
}
// Evaluate and push arguments in reverse order
for (i, arg_reg) in arg_regs.iter().enumerate() {
code.push(format!(
"\tpush {} // push arg {}",
arg_reg,
args.len() - 1 - i
));
}
// if GLOBAL_METHODS.contains_key(name.name.as_str()) {
// code.push(format!("\tcall {}",
// GLOBAL_METHODS[name.name.as_str()])); } else
if self.symbols.contains(&name.name) {
// Call local function
code.push(format!("\tcall {}", name));
} else if let Some(ns) = name.namespace.clone()
&& self.imports.contains_key(&ns)
{
code.push(format!("\tcall {}", name));
} else {
return Err(CompilerError::Undefined(name.clone()));
}
let result_reg: String;
if use_result {
let (temp_result_reg, result_alloc) = self.allocator.alloc_temp()?;
result_reg = temp_result_reg;
code.extend(result_alloc);
code.push(format!("\tpop {}", result_reg));
// Clean up arguments
if args.len() > 1 {
for _ in 0..(args.len() - 1) {
code.push("\tpop zero".to_string());
}
}
} else {
result_reg = "zero".to_string();
// Clean up arguments
if args.len() > 0 {
for _ in 0..(args.len()) {
code.push("\tpop zero".to_string());
}
}
}
// Restore caller-saved registers in reverse order (LIFO)
// for reg in saved_regs.iter().rev() {
// code.push(format!("\tpop {}", reg));
// }
// Free argument registers
for reg in arg_regs {
self.allocator.free_temp(&reg);
}
Ok((result_reg, code))
}
Expression::Unary { op, operand } => {
let (operand_reg, operand_code) =
self.generate_expression(operand, true)?;
code.extend(operand_code);
let (result_reg, result_alloc) = self.allocator.alloc_temp()?;
code.extend(result_alloc);
match op {
UnaryOperator::Minus => {
// Negate: result = 0 - operand
code.push(format!("\tsub zero, {}, {}", operand_reg, result_reg));
}
UnaryOperator::Plus => {
// Just move
code.push(format!("\tmov {}, {}", operand_reg, result_reg));
}
UnaryOperator::Dereference => {
code.push(format!("\tldw {}, {}", operand_reg, result_reg));
}
UnaryOperator::Reference => {
code.extend(self.allocator.spill_register(&operand_reg)?);
code.push(format!(
"\tsubi bpr {} {}",
-(4 + self.allocator.get_stack_offset()),
result_reg
))
}
}
self.allocator.free_temp(&operand_reg);
Ok((result_reg, code))
}
Expression::Empty => Ok(("zero".to_string(), 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()
}
}
/// Build a single string from any number of arguments.
/// Each argument must implement `Display` or be convertible to a string.
#[macro_export]
macro_rules! dsa {
($($arg:expr),* $(,)?) => {{
// Start with an empty String well grow it as we go.
use std::fmt::Write;
let mut s = ::std::string::String::new();
$(
// `write!` is cheaper than `format!` for each element
// because it reuses the same buffer.
write!(s, "{}\n", $arg).expect("write to String failed");
)*
s
}};
}
// ──────────────────────── dsa! ────────────────────────
// A tiny helper that just turns its tokenstream into a string.
// The trailing comma is kept its part of the syntax you want.
#[macro_export]
macro_rules! cmd {
($($tokens:tt)*) => {{
// Well just stringify the tokens and return a String.
format!("{}", concat!(stringify!($tokens), "\n"))
}};
}
// ──────────────────────── block! ────────────────────────
// Usage:
//
// let asm = block![ "name"
// dsa![mov rg0, rg1],
// dsa![add rg1, rg1]
// ];
//
// `asm` is a `&'static str` containing:
//
// name:
// mov rg0, rg1
// add rg1, rg1
//
#[macro_export]
macro_rules! block {
// The first token must be a string literal thats the label.
($label:literal $(dsa![$($ins:tt)*]),* ) => {{
// Build a single string at compile time.
const CODE: &str = concat!(
$label, ":\n",
// Each `dsa!` call yields a string like `"mov rg0, rg1"`.
// We add a newline after each one to get the desired layout.
$(concat!("\t", stringify!($($ins)*), "\n")),*
);
CODE
}};
}
#[macro_export]
macro_rules! comment {
($text:expr) => {{ format!("// {}", $text) }};
}
-627
View File
@@ -1,627 +0,0 @@
use std::iter::Peekable;
use std::str::Chars;
#[derive(Debug, PartialEq, Clone)]
pub enum Token {
// Keywords
Fn,
Let,
If,
Else,
Loop,
While,
Break,
Return,
Continue,
Include,
Static,
Const,
// Identifiers and literals
Identifier(Name),
String(String),
Integer(u64),
Char(char),
// Symbols
LeftParen, // (
RightParen, // )
LeftBrace, // {
RightBrace, // }
Semicolon, // ;
Colon, // :
Comma, // ,
// Operators
Plus, // +
Minus, // -
Star, // *
Amphersand, // &
Slash, // /
Assign, // =
EqualEqual, // ==
Bang, // !
BangEqual, // !=
Less, // <
LessEqual, // <=
Greater, // >
GreaterEqual, // >=
RightArrow, // ->
// Special
Eof,
}
#[derive(Debug, PartialEq, Clone)]
pub struct Name {
pub name: String,
pub namespace: Option<String>,
}
use std::fmt;
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref ns) = self.namespace {
write!(f, "{}::{}", ns, self.name)
} else {
write!(f, "{}", self.name)
}
}
}
impl Token {
pub fn tt(&self) -> &str {
match self {
Token::Const => "Const",
Token::Static => "Static",
Token::Include => "Include",
Token::Fn => "Fn",
Token::If => "If",
Token::Let => "Let",
Token::Else => "Else",
Token::Loop => "Loop",
Token::While => "While",
Token::Break => "Break",
Token::Return => "Return",
Token::Continue => "Continue",
Token::Identifier(_) => "Identifier",
Token::String(_) => "String",
Token::Integer(_) => "UnsignedInt",
Token::Char(_) => "Char",
Token::LeftParen => "LeftParen",
Token::RightParen => "RightParen",
Token::LeftBrace => "LeftBrace",
Token::RightBrace => "RightBrace",
Token::Semicolon => "Semicolon",
Token::Colon => "Colon",
Token::Comma => "Comma",
Token::RightArrow => "RightArrow",
Token::Plus => "Plus",
Token::Minus => "Minus",
Token::Star => "Star",
Token::Amphersand => "Amphersand",
Token::Slash => "Slash",
Token::Assign => "Assign",
Token::EqualEqual => "EqualEqual",
Token::Bang => "Bang",
Token::BangEqual => "BangEqual",
Token::Less => "Less",
Token::LessEqual => "LessEqual",
Token::Greater => "Greater",
Token::GreaterEqual => "GreaterEqual",
Token::Eof => "Eof",
}
}
}
#[derive(Debug)]
pub struct Lexer<'a> {
chars: Peekable<Chars<'a>>,
current: Option<char>,
line: usize,
}
impl<'a> Lexer<'a> {
pub fn new(input: &'a str) -> Self {
let mut chars = input.chars().peekable();
let current = chars.next();
Lexer {
chars,
current,
line: 1,
}
}
fn advance(&mut self) -> Option<char> {
self.current = self.chars.next();
self.current
}
fn peek(&mut self) -> Option<&char> {
self.chars.peek()
}
fn skip_whitespace(&mut self) {
while let Some(c) = self.current {
if !c.is_whitespace() {
break;
}
if c == '\n' {
self.line += 1;
}
self.advance();
}
}
fn skip_line_comment(&mut self) {
// Skip the two slashes
self.advance(); // first /
self.advance(); // second /
// Skip until newline or EOF
while let Some(c) = self.current {
if c == '\n' {
self.line += 1;
self.advance();
break;
}
self.advance();
}
}
fn skip_block_comment(&mut self) -> Result<(), String> {
// Skip the /*
self.advance(); // /
self.advance(); // *
let start_line = self.line;
// Look for */
while let Some(c) = self.current {
if c == '\n' {
self.line += 1;
}
if c == '*' {
if let Some(&next) = self.peek() {
if next == '/' {
self.advance(); // *
self.advance(); // /
return Ok(());
}
}
}
self.advance();
}
Err(format!(
"Unterminated block comment starting at line {}",
start_line
))
}
fn skip_whitespace_and_comments(&mut self) {
loop {
self.skip_whitespace();
// Check for comments
if let Some('/') = self.current {
if let Some(&next) = self.peek() {
match next {
'/' => {
self.skip_line_comment();
continue;
}
'*' => {
if let Err(e) = self.skip_block_comment() {
eprintln!("Lexer error: {}", e);
}
continue;
}
_ => break,
}
}
}
break;
}
}
fn read_identifier(&mut self) -> String {
let mut ident = String::new();
// Include the current character if it's valid
if let Some(c) = self.current {
if c.is_alphabetic() || c == '_' {
ident.push(c);
}
}
// Read remaining characters
while let Some(&c) = self.peek() {
if c.is_alphanumeric() || c == '_' {
self.advance();
ident.push(c);
} else {
break;
}
}
ident
}
fn keyword_or_identifier(&mut self) -> Token {
let first_ident = self.read_identifier();
// Check if it's a keyword first (keywords can't have namespaces)
let keyword = match first_ident.as_str() {
"fn" => Some(Token::Fn),
"if" => Some(Token::If),
"else" => Some(Token::Else),
"while" => Some(Token::While),
"loop" => Some(Token::Loop),
"break" => Some(Token::Break),
"return" => Some(Token::Return),
"continue" => Some(Token::Continue),
"include" => Some(Token::Include),
"let" => Some(Token::Let),
"const" => Some(Token::Const),
"static" => Some(Token::Static),
_ => None,
};
if let Some(kw) = keyword {
return kw;
}
// Not a keyword - check for namespace separator (::)
// We need to peek TWO characters ahead without consuming anything
if let Some(&':') = self.peek() {
// We see one colon, but we need to check if there's another one after it
// We can't peek two ahead directly, so we need a different approach
// Save the current position by using a temporary peekable iterator
// Actually, we can't do that easily. Instead, let's just check:
// If we see ':', temporarily advance and check the next char
// Create a temporary check
let mut temp_chars = self.chars.clone();
let first_peek = temp_chars.next(); // This is the ':' we already saw
let second_peek = temp_chars.peek();
if let Some(&':') = second_peek {
// It's :: - consume both colons
self.advance(); // consume first :
self.advance(); // consume second :
// Read the second identifier (the actual name)
let second_ident = self.read_identifier();
// Return namespaced identifier
return Token::Identifier(Name {
namespace: Some(first_ident),
name: second_ident,
});
}
// else: It's a single colon (type annotation) - DON'T consume it
// Just fall through and return the identifier
}
// No namespace separator - just a regular identifier
Token::Identifier(Name {
namespace: None,
name: first_ident,
})
}
fn read_number(&mut self) -> Result<u64, String> {
let current = self.current.unwrap();
// Check for hex (0x) or binary (0b) prefix
if current == '0' {
if let Some(&next_char) = self.peek() {
match next_char {
'x' | 'X' => {
self.advance(); // consume '0'
self.advance(); // consume 'x'
return self.read_hex_number();
}
'b' | 'B' => {
self.advance(); // consume '0'
self.advance(); // consume 'b'
return self.read_binary_number();
}
_ => {}
}
}
}
// Read decimal number
self.read_decimal_number()
}
fn read_decimal_number(&mut self) -> Result<u64, String> {
let mut num_str = String::new();
if let Some(c) = self.current {
num_str.push(c);
}
while let Some(&c) = self.peek() {
if c.is_ascii_digit() {
self.advance();
num_str.push(c);
} else {
break;
}
}
num_str
.parse::<u64>()
.map_err(|_| format!("Invalid decimal number: {}", num_str))
}
fn read_hex_number(&mut self) -> Result<u64, String> {
let mut num_str = String::new();
// Read current character if it's a hex digit
if let Some(c) = self.current {
if c.is_ascii_hexdigit() {
num_str.push(c);
}
}
while let Some(&c) = self.peek() {
if c.is_ascii_hexdigit() {
self.advance();
num_str.push(c);
} else {
break;
}
}
if num_str.is_empty() {
return Err("Invalid hexadecimal number: no digits after 0x".to_string());
}
u64::from_str_radix(&num_str, 16)
.map_err(|_| format!("Invalid hexadecimal number: {}", num_str))
}
fn read_binary_number(&mut self) -> Result<u64, String> {
let mut num_str = String::new();
// Read current character if it's a binary digit
if let Some(c) = self.current {
if c == '0' || c == '1' {
num_str.push(c);
}
}
while let Some(&c) = self.peek() {
if c == '0' || c == '1' {
self.advance();
num_str.push(c);
} else {
break;
}
}
if num_str.is_empty() {
return Err("Invalid binary number: no digits after 0b".to_string());
}
u64::from_str_radix(&num_str, 2)
.map_err(|_| format!("Invalid binary number: {}", num_str))
}
fn read_string(&mut self) -> Result<String, String> {
self.advance(); // Skip the opening quote
let mut s = String::new();
while let Some(c) = self.current {
if c == '"' {
return Ok(s);
}
// Handle escape sequences
if c == '\\' {
self.advance();
if let Some(escaped) = self.current {
let escaped_char = match escaped {
'n' => '\n',
't' => '\t',
'r' => '\r',
'\\' => '\\',
'"' => '"',
_ => escaped, // For now, just use the character as-is
};
s.push(escaped_char);
} else {
return Err("Unexpected end of string after escape".to_string());
}
} else {
s.push(c);
}
self.advance();
}
Err("Unterminated string literal".to_string())
}
fn match_next(&mut self, expected: char) -> bool {
match self.peek() {
Some(&c) if c == expected => {
self.advance();
true
}
_ => false,
}
}
fn scan_single_char_token(&mut self, c: char) -> Option<Token> {
match c {
'(' => Some(Token::LeftParen),
')' => Some(Token::RightParen),
'{' => Some(Token::LeftBrace),
'}' => Some(Token::RightBrace),
';' => Some(Token::Semicolon),
',' => Some(Token::Comma),
'&' => Some(Token::Amphersand),
'+' => Some(Token::Plus),
'*' => Some(Token::Star),
_ => None,
}
}
fn scan_operator(&mut self, c: char) -> Option<Token> {
match c {
'-' => Some(if self.match_next('>') {
Token::RightArrow
} else {
Token::Minus
}),
'!' => Some(if self.match_next('=') {
Token::BangEqual
} else {
Token::Bang
}),
'=' => Some(if self.match_next('=') {
Token::EqualEqual
} else {
Token::Assign
}),
'<' => Some(if self.match_next('=') {
Token::LessEqual
} else {
Token::Less
}),
'>' => Some(if self.match_next('=') {
Token::GreaterEqual
} else {
Token::Greater
}),
':' => {
// Single colon (for type annotations)
// Note: :: is handled in keyword_or_identifier for namespaces
Some(Token::Colon)
}
'/' => {
// Check if it's a comment or division
if let Some(&next) = self.peek() {
if next == '/' || next == '*' {
// It's a comment, don't consume it here
// Let skip_whitespace_and_comments handle it
None
} else {
Some(Token::Slash)
}
} else {
Some(Token::Slash)
}
}
_ => None,
}
}
pub fn next_token(&mut self) -> Token {
self.skip_whitespace_and_comments();
let Some(c) = self.current else {
return Token::Eof;
};
// Try single-character tokens first
if let Some(token) = self.scan_single_char_token(c) {
self.advance();
return token;
}
// Try operators (may be multi-character)
if let Some(token) = self.scan_operator(c) {
self.advance();
return token;
}
// String literals
if c == '"' {
let token = match self.read_string() {
Ok(s) => Token::String(s),
Err(e) => {
eprintln!("Lexer error on line {}: {}", self.line, e);
// Skip to next quote or end
while let Some(ch) = self.current {
if ch == '"' || ch == '\n' {
break;
}
self.advance();
}
Token::String(String::new())
}
};
self.advance();
return token;
}
// Identifiers and keywords (including namespaced identifiers)
if c.is_alphabetic() || c == '_' {
let token = self.keyword_or_identifier();
self.advance();
return token;
}
// Numbers (decimal, hex, binary)
if c.is_ascii_digit() {
let token = match self.read_number() {
Ok(num) => Token::Integer(num),
Err(e) => {
eprintln!("Lexer error on line {}: {}", self.line, e);
// Skip invalid number
while let Some(&ch) = self.peek() {
if !ch.is_alphanumeric() {
break;
}
self.advance();
}
Token::Integer(0)
}
};
self.advance();
return token;
}
// Unknown character - skip it
eprintln!(
"Lexer warning on line {}: Skipping unknown character '{}'",
self.line, c
);
self.advance();
self.next_token()
}
}
impl<'a> Iterator for Lexer<'a> {
type Item = Token;
fn next(&mut self) -> Option<Self::Item> {
match self.next_token() {
Token::Eof => None,
token => Some(token),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic() {
// Placeholder test
assert!(true);
}
}
-74
View File
@@ -1,74 +0,0 @@
#![feature(try_trait_v2)]
use std::path::Path;
use common::logging::log;
use crate::{
codegen::CodeGenerator,
parser::{ParseResult, Parser},
semantic_analyser::Analyser,
};
mod codegen;
mod lexer;
mod parser;
mod registers;
mod semantic_analyser;
pub fn compile_file(
input_path: &Path,
output_path: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
let input = std::fs::read_to_string(input_path).expect("Failed to read input file");
log("Tokenising Input...");
let lexer = lexer::Lexer::new(&input);
let tokens = lexer.collect::<Vec<_>>();
// println!("{tokens:?}");
log(&format!("Parsing {} Tokens...", tokens.len()));
let mut parser = Parser::new(tokens);
let ast = match parser.parse() {
ParseResult::Accept(ast) => ast,
ParseResult::Reject(e) => {
eprintln!("Error: {e:?}");
return Err("Parsing error".into());
}
ParseResult::Deny => {
panic!("Parser denied parsing")
}
};
// println!("{ast:#?}");
log("Analyzing AST...");
log("Checking Type Information...");
let analyser = Analyser::new();
analyser.analyse(ast.clone()).unwrap();
log("Generating Code...");
// Code Gen
let mut generator = CodeGenerator::new(ast);
let result = match generator.generate() {
Ok(code) => code,
Err(e) => {
eprintln!("Parsing error: {:?}", e);
return Err("Code generation error".into());
}
};
// println!("{result}");
std::fs::write(output_path, &result).expect("Failed to write output");
log(&format!(
"Compilation Successful ✅ \n\tSource: {}\n\tOutput: {}\n",
input_path.display(),
output_path.display(),
));
Ok(())
}
-21
View File
@@ -1,21 +0,0 @@
use std::path::Path;
use 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.c> [output.dsa]");
return;
}
let input_file = &args[1];
let output_file = if args.len() > 2 {
&args[2]
} else {
"output.dsa"
};
compiler::compile_file(Path::new(input_file), Path::new(output_file)).unwrap();
}
-790
View File
@@ -1,790 +0,0 @@
use crate::lexer::{Name, Token};
use crate::{expect_tt, expect_value};
use core::fmt;
use std::ops::{ControlFlow, FromResidual, Try};
#[derive(Debug, Clone)]
pub enum ParseResult<T, E> {
Accept(T),
Deny,
Reject(E),
}
#[derive(Debug, Clone)]
pub enum CompilerError {
UnexpectedToken(Token),
UnexpectedEndOfInput,
UnexpectedCharacter(char),
Undefined(Name),
InvalidSyntax(String),
Generic(String),
}
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()?, 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::Integer(x) => Some(ConstExpr::Number(x as i32)),
_ => return ParseResult::Reject(CompilerError::UnexpectedToken(value)),
};
let _ = expect_tt!(self.next()?, Semicolon)?;
return ParseResult::Accept(Declaration::Variable {
var,
init,
is_const,
});
}
ParseResult::Reject(CompilerError::UnexpectedEndOfInput)
}
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()?))
}
}
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()?,
));
};
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 assignment without "let"
let name = expect_value!(self.peek_next()?, Identifier);
if name.accepted() {
let varname = name?;
if expect_tt!(self.peek(1)?, LeftParen).accepted() {
let expr = self.parse_expression()?; // a function call expr
let _ = expect_tt!(self.next()?, Semicolon)?;
return ParseResult::Accept(Statement::Expression { expr });
}
self.next()?;
let _ = expect_tt!(self.next()?, Assign)?;
let value = self.parse_expression()?;
let _ = expect_tt!(self.next()?, Semicolon);
return ParseResult::Accept(Statement::Assign {
varname: varname.name,
value,
});
}
ParseResult::Reject(CompilerError::UnexpectedToken(self.peek_next()?))
}
fn parse_expression(&mut self) -> ParseResult<Expression, CompilerError> {
self.parse_comparison()
}
fn parse_comparison(&mut self) -> ParseResult<Expression, CompilerError> {
let mut expr = self.parse_additive()?;
while let Some(op) = match self.peek_next()? {
Token::EqualEqual => Some(BinaryOperator::Ne),
Token::BangEqual => Some(BinaryOperator::Ne),
Token::Less => Some(BinaryOperator::Lt),
Token::Greater => Some(BinaryOperator::Gt),
Token::LessEqual => Some(BinaryOperator::Le),
Token::GreaterEqual => Some(BinaryOperator::Ge),
_ => None,
} {
self.next()?;
let right = Box::new(self.parse_additive()?);
expr = Expression::Binary {
op,
left: Box::new(expr),
right,
}
}
ParseResult::Accept(expr)
}
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()?),
})
}
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()?),
})
}
fn parse_unary(&mut self) -> ParseResult<Expression, CompilerError> {
let op = match self.peek_next()? {
Token::Plus => UnaryOperator::Plus,
Token::Minus => UnaryOperator::Minus,
Token::Star => UnaryOperator::Dereference,
Token::Amphersand => UnaryOperator::Reference,
_ => return ParseResult::Accept(self.parse_primary()?),
};
self.next()?;
let operand = Box::new(self.parse_unary()?);
ParseResult::Accept(Expression::Unary { op, operand })
}
fn parse_primary(&mut self) -> ParseResult<Expression, CompilerError> {
match self.peek_next()? {
Token::Integer(value) => {
self.next()?;
ParseResult::Accept(Expression::Number(value as isize))
}
Token::String(value) => {
self.next()?;
ParseResult::Accept(Expression::StringLiteral(value))
}
Token::Identifier(_) => {
let name = expect_value!(self.next()?, Identifier)?;
if matches!(self.peek_next()?, Token::LeftParen) {
// Function call
self.next()?;
let mut args = Vec::new();
if !matches!(self.peek_next()?, Token::RightParen) {
args.push(self.parse_expression()?);
while matches!(self.peek_next()?, Token::Comma) {
self.next()?;
args.push(self.parse_expression()?);
}
}
let _ = expect_tt!(self.next()?, RightParen)?;
ParseResult::Accept(Expression::Call { name, args })
} else {
ParseResult::Accept(Expression::Variable {
name,
expr_type: 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()?)),
}
}
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> {
// get the type name incl namespace
let typename = expect_value!(self.next()?, Identifier)?;
match typename.name.as_str() {
"u32" => ParseResult::Accept(TypeId::U32),
"u16" => ParseResult::Accept(TypeId::U16),
"u8" => ParseResult::Accept(TypeId::U8),
"i32" => ParseResult::Accept(TypeId::I32),
"i16" => ParseResult::Accept(TypeId::I16),
"i8" => ParseResult::Accept(TypeId::I8),
"void" => ParseResult::Accept(TypeId::Void),
"char" => ParseResult::Accept(TypeId::Char),
"str" => ParseResult::Accept(TypeId::Ptr(Box::new(TypeId::Char))),
_ => todo!("Implement parsing for other types!!"),
}
}
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())
}
}
}
#[derive(Debug, Clone)]
pub struct Program {
pub declarations: Vec<Declaration>,
}
#[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),
}
#[derive(Debug, Clone)]
pub struct Dependency {
pub name: String,
pub path: String,
}
#[derive(Debug, Clone)]
pub enum TypeId {
U8,
U16,
U32,
I8,
I16,
I32,
Char,
Void,
Ptr(Box<TypeId>),
Ref(Box<TypeId>),
Array(Box<TypeId>, usize),
Struct { name: Name, fields: Vec<Variable> },
}
pub type Block = Vec<Statement>;
#[derive(Debug, Clone)]
pub struct Variable {
pub name: String,
pub type_id: TypeId,
}
#[derive(Debug, Clone)]
pub enum Statement {
Block(Block),
Declaration {
var: Variable,
value: Option<Expression>,
},
Assign {
varname: String,
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),
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),
}
}
}
#[derive(Debug, Clone)]
pub enum Expression {
Empty,
Binary {
op: BinaryOperator,
left: Box<Expression>,
right: Box<Expression>,
},
Unary {
op: UnaryOperator,
operand: Box<Expression>,
},
Variable {
name: Name,
expr_type: Option<TypeId>,
},
Call {
name: Name,
args: Vec<Expression>,
},
Number(isize),
StringLiteral(String),
CharLiteral(char),
}
impl Expression {
pub fn is_pure(&self) -> bool {
match self {
Expression::Number(_) => true,
Expression::StringLiteral(_) => true,
Expression::CharLiteral(_) => true,
Expression::Call { name, args } => false, /* TODO: will require checking */
// if the associated function
// body is pure
Expression::Binary { left, right, .. } => left.is_pure() && right.is_pure(),
Expression::Unary { op, operand } => operand.is_pure(),
Expression::Empty => true,
Expression::Variable { name, expr_type } => true,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BinaryOperator {
Add,
Sub,
Mul,
Div,
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
}
impl fmt::Display for BinaryOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
BinaryOperator::Add => write!(f, "+"),
BinaryOperator::Sub => write!(f, "-"),
BinaryOperator::Mul => write!(f, "*"),
BinaryOperator::Div => write!(f, "/"),
BinaryOperator::Eq => write!(f, "=="),
BinaryOperator::Ne => write!(f, "!="),
BinaryOperator::Lt => write!(f, "<"),
BinaryOperator::Gt => write!(f, ">"),
BinaryOperator::Le => write!(f, "<="),
BinaryOperator::Ge => write!(f, ">="),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum UnaryOperator {
Plus,
Minus,
Reference,
Dereference,
}
impl fmt::Display for UnaryOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
UnaryOperator::Plus => write!(f, "+"),
UnaryOperator::Minus => write!(f, "-"),
UnaryOperator::Dereference => write!(f, "*"),
UnaryOperator::Reference => write!(f, "&"),
}
}
}
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(token))
}
}
}};
}
#[macro_export]
macro_rules! expect_value {
($expr:expr, $variant:ident) => {{
let tok = $expr;
match tok.clone() {
Token::$variant(value) => ParseResult::Accept(value),
_ => ParseResult::Reject(CompilerError::UnexpectedToken(tok)),
}
}};
}
-398
View File
@@ -1,398 +0,0 @@
use std::collections::HashMap;
use crate::parser::CompilerError;
/// Register allocator for DSA assembly generation
/// Manages general-purpose registers (rg0-rgf) and handles stack spilling
pub struct RegisterAllocator {
/// Available general-purpose registers
available_registers: Vec<String>,
/// 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<String, 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: HashMap<String, bool>,
}
#[derive(Debug, Clone)]
pub enum Location {
Register(String),
Stack(i32), // offset from bpr
}
impl RegisterAllocator {
pub fn new() -> Self {
// Initialize with available GP registers (rg0-rgf = 16 registers)
let registers = vec![
"rg0", "rg1", "rg2", "rg3", "rg4", "rg5", "rg6", "rg7", "rg8", "rg9", "rga",
"rgb", "rgc", "rgd", "rge", "rgf",
]
.into_iter()
.map(String::from)
.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: HashMap::new(),
}
}
/// 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<(String, Vec<String>), CompilerError> {
let mut code = Vec::new();
// Try to find an unused register
for reg in &self.available_registers {
if !self.in_use.get(reg).unwrap_or(&false) {
self.in_use.insert(reg.clone(), true);
return Ok((reg.clone(), code));
}
}
// 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(&reg)?;
code.extend(spill_code);
self.in_use.insert(reg.clone(), true);
return Ok((reg, code));
}
Err(CompilerError::Generic(
"All registers are used up yet there are no variables to spill to the stack"
.to_string(),
))
}
/// 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: &str) {
// Check if this register contains a variable
if self.register_contents.contains_key(reg) {
// 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
self.in_use.insert(reg.to_string(), false);
}
/// 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<(String, Vec<String>), CompilerError> {
if let Some(location) = self.variable_locations.get(var_name).cloned() {
match location {
Location::Register(reg) => {
return Ok((reg.clone(), Vec::new()));
}
Location::Stack(offset) => {
// Variable was pushed, need to calculate actual position
let (reg, mut code) = self.alloc_temp()?;
// Load from bpr + offset (offset is negative)
code.push(format!("\tsubi bpr {} {}", -(offset + 4), reg));
code.push(format!(
"\tldw {}, {} // bpr{}: {}",
reg,
reg,
offset - 4,
var_name
));
// Update location to register
self.variable_locations
.insert(var_name.to_string(), Location::Register(reg.clone()));
self.register_contents
.insert(reg.clone(), 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.clone()));
self.register_contents
.insert(reg.clone(), 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<(String, Vec<String>), 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: &str) -> Vec<String> {
let mut code = Vec::new();
// Check if variable already has a location
if let Some(location) = self.variable_locations.get(var_name) {
match location {
Location::Register(dest_reg) => {
if dest_reg != source_reg {
code.push(format!(
"\tmov {}, {} // var {}",
source_reg, dest_reg, var_name
));
}
}
Location::Stack(offset) => {
code.push(format!(
"\tstw {}, bpr, {} // var {}",
source_reg, offset, var_name
));
}
}
} else {
// Variable doesn't exist yet, we can just use the same reg.
// self.variable_locations.insert(
// var_name.to_string(),
// Location::Register(source_reg.to_string()),
// );
// self.register_contents
// .insert(source_reg.to_string(), var_name.to_string());
// self.in_use.insert(source_reg.to_string(), true);
let source_reg = source_reg.to_string();
// if we can avoid a move, absolutely do that.
if self.available_registers.contains(&source_reg) {
self.variable_locations
.insert(var_name.to_string(), Location::Register(source_reg.clone()));
self.register_contents
.insert(source_reg.clone(), var_name.to_string());
self.in_use.insert(source_reg, true);
} else if let Some(free_reg) = self.find_free_register() {
code.push(format!("\tmov {}, {}", source_reg, free_reg));
self.variable_locations
.insert(var_name.to_string(), Location::Register(free_reg.clone()));
self.register_contents
.insert(free_reg.clone(), var_name.to_string());
self.in_use.insert(free_reg, true);
} else {
// 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!(
"we should spill other registers and keep this variable on the stack as it's more recent!"
);
}
}
code
}
/// Spill a register to the stack
/// Returns assembly code to perform the spill
pub fn spill_register(&mut self, reg: &str) -> Result<Vec<String>, CompilerError> {
let mut code = Vec::new();
if let Some(var_name) = self.register_contents.get(reg).cloned() {
// PUSH register to stack (spr decrements automatically)
code.push(format!(
"\tpush {} // bpr{}: {}",
reg, self.stack_offset, var_name
));
// Track that we pushed one word
self.stack_offset -= 4;
// Update variable location - it's now at current spr
// Note: We track offset from bpr for consistency
self.variable_locations
.insert(var_name.clone(), Location::Stack(self.stack_offset));
// Remove from register tracking
self.register_contents.remove(reg);
}
Ok(code)
}
/// Find a free register (not currently in use)
fn find_free_register(&self) -> Option<String> {
for reg in &self.available_registers {
if !self.in_use.get(reg).unwrap_or(&false) {
return Some(reg.clone());
}
}
None
}
/// Spill all registers to stack (useful before function calls)
pub fn spill_all(&mut self) -> Vec<String> {
let mut code = Vec::new();
let regs_to_spill: Vec<String> = self.register_contents.keys().cloned().collect();
for reg in regs_to_spill {
if let Ok(spill_code) = self.spill_register(&reg) {
code.extend(spill_code);
}
}
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.clear();
}
/// Mark a variable as dead (no longer needed)
/// Frees its register if it's in one
pub fn free_var(&mut self, var_name: &str) {
if let Some(Location::Register(reg)) = self.variable_locations.get(var_name) {
let reg = reg.clone();
self.register_contents.remove(&reg);
self.in_use.insert(reg, false);
}
self.variable_locations.remove(var_name);
}
/// 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<String> {
self.register_contents
.iter()
.filter(|(reg, _)| *self.in_use.get(*reg).unwrap_or(&false))
.map(|(reg, _)| reg.clone())
.collect()
}
/// Save caller-saved registers before a function call
/// Returns assembly code to save them
pub fn save_caller_saved(&mut self) -> Vec<String> {
let mut code = Vec::new();
// For simplicity, save all currently used registers
// In a more sophisticated compiler, you'd only save registers that are live
for (reg, var_name) in self.register_contents.clone() {
if *self.in_use.get(&reg).unwrap_or(&false) {
code.push(format!("\tpush {}", reg));
}
}
code
}
/// Restore caller-saved registers after a function call
/// Returns assembly code to restore them
pub fn restore_caller_saved(&mut self, saved_regs: &[String]) -> Vec<String> {
let mut code = Vec::new();
// Restore in reverse order (LIFO)
for reg in saved_regs.iter().rev() {
code.push(format!("\tpop {}", reg));
}
code
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_allocation() {
let mut allocator = RegisterAllocator::new();
let (reg1, code1) = allocator.alloc_temp().unwrap();
assert_eq!(code1.len(), 0); // No spill needed
assert_eq!(reg1, "rg0");
let (reg2, code2) = allocator.alloc_temp().unwrap();
assert_eq!(code2.len(), 0);
assert_eq!(reg2, "rg1");
allocator.free_temp(&reg1);
let (reg3, code3) = allocator.alloc_temp().unwrap();
assert_eq!(code3.len(), 0);
assert_eq!(reg3, "rg0"); // Reuses freed register
}
#[test]
fn test_variable_allocation() {
let mut allocator = RegisterAllocator::new();
let (reg, _) = allocator.alloc_var("x").unwrap();
assert_eq!(reg, "rg0");
// Requesting same variable again should return same register
let (reg2, _) = allocator.alloc_var("x").unwrap();
assert_eq!(reg2, "rg0");
}
#[test]
fn test_stack_allocation() {
let mut allocator = RegisterAllocator::new();
// Allocate all 16 registers
for i in 0..16 {
allocator.alloc_var(&format!("var{}", i)).unwrap();
}
// Next allocation should spill to stack
let (reg, code) = allocator.alloc_var("var16").unwrap();
assert!(code.len() > 0); // Should have spill code
}
}
-13
View File
@@ -1,13 +0,0 @@
use crate::parser::{CompilerError, Program};
pub struct Analyser;
impl Analyser {
pub fn new() -> Self {
Self
}
pub fn analyse(&self, ast: Program) -> Result<(), CompilerError> {
Ok(())
}
}
+3985
View File
File diff suppressed because it is too large Load Diff
+6 -5
View File
@@ -160,11 +160,12 @@ impl CodeEditor {
/// Stick to bottom
/// The scroll handle will stick to the bottom position even while the content size
/// changes dynamically. This can be useful to simulate terminal UIs or log/info scrollers.
/// The scroll handle remains stuck until user manually changes position. Once "unstuck"
/// it will remain focused on whatever content viewport the user left it on. If the scroll
/// handle is dragged to the bottom it will again become stuck and remain there until manually
/// pulled from the end position.
/// changes dynamically. This can be useful to simulate terminal UIs or log/info
/// scrollers. The scroll handle remains stuck until user manually changes
/// position. Once "unstuck" it will remain focused on whatever content viewport
/// the user left it on. If the scroll handle is dragged to the bottom it will
/// again become stuck and remain there until manually pulled from the end
/// position.
///
/// **Default: false**
pub fn stick_to_bottom(self, stick_to_bottom: bool) -> Self {
-1
View File
@@ -16,7 +16,6 @@ required-features = ["config"]
[dependencies]
common = { path = "../common" }
assembler = { path = "../assembler" }
compiler = { path = "../compiler" }
dsa_editor = { path = "../dsa_editor" }
egui = "0.31.1"
dirs = "6.0.0"
+3 -3
View File
@@ -180,12 +180,12 @@ impl Drop for RpcClient {
fn drop(&mut self) {
self.stop();
if let Some(handle) = self.thread_handle.take() {
if let Some(handle) = Arc::into_inner(handle) {
if let Some(handle) = self.thread_handle.take()
&& let Some(handle) = Arc::into_inner(handle)
{
let _ = handle.join();
}
}
}
}
/// Stub for when the feature is disabled.
+6 -17
View File
@@ -110,6 +110,7 @@ pub fn run_emulator(
);
});
}
#[expect(unused_assignments)]
Command::Interrupt(_interrupt) => {
update = true;
@@ -190,9 +191,7 @@ pub fn run_emulator(
history.push((addr, instruction));
}
Err(why) => {
let pcx = processor
.get(Register::Pcx)
.expect("SPR should never be invalid");
let pcx = processor.get(Register::Pcx);
report_err(
state_tx,
&format!(
@@ -213,17 +212,9 @@ pub fn run_emulator(
let instruction = match processor.cycle() {
Ok(instruction) => instruction,
Err(why) => {
let pcx = processor
.get(Register::Pcx)
.expect("PCX should never be invalid");
report_err(
state_tx,
&format!(
"Could not decode instruction at {pcx:x}. Reason: {why}"
),
&mut processor,
);
(pcx, Instruction::Nop)
let pcx = processor.get(Register::Pcx);
eprintln!("Could not decode instruction at {pcx:x}. Reason: {why}");
continue;
}
};
@@ -238,8 +229,6 @@ pub fn run_emulator(
}
fn report_err(state_tx: &Sender<StateUpdate>, why: &str, processor: &mut Processor) {
processor
.begin_interrupt(Interrupt::HardFault)
.expect("What kind of goofy ahh shenanigans did you do with your fault handler? At this point, the emulator can just crash. this is on you.");
processor.begin_interrupt(Interrupt::HardFault);
let _ = state_tx.send(StateUpdate::Error(why.to_string()));
}
+8 -8
View File
@@ -257,8 +257,8 @@ impl RegFile {
self.pcx = 0;
}
pub const fn reg(&mut self, reg: Register) -> Result<&mut u32, ProcessorError> {
Ok(match reg {
pub fn reg(&mut self, reg: Register) -> &mut u32 {
match reg {
Register::Rg0 => &mut self.rg0,
Register::Rg1 => &mut self.rg1,
Register::Rg2 => &mut self.rg2,
@@ -286,13 +286,13 @@ impl RegFile {
Register::Sts => &mut self.sts,
Register::Cir => &mut self.cir,
Register::Pcx => &mut self.pcx,
_ => return Err(ProcessorError::InvalidRegister(Register::NoReg as u8)),
})
_ => panic!("Invalid register."),
}
}
#[must_use]
pub const fn get(&self, reg: Register) -> Result<u32, ProcessorError> {
Ok(match reg {
pub fn get(&self, reg: Register) -> u32 {
match reg {
Register::Rg0 => self.rg0,
Register::Rg1 => self.rg1,
Register::Rg2 => self.rg2,
@@ -321,7 +321,7 @@ impl RegFile {
Register::Cir => self.cir,
Register::Pcx => self.pcx,
Register::Zero => 0,
_ => return Err(ProcessorError::InvalidRegister(Register::NoReg as u8)),
})
_ => panic!("Invalid register."),
}
}
}
+86 -115
View File
@@ -16,9 +16,10 @@ pub struct Processor {
pub halted: bool,
pub io_devices: Vec<Arc<dyn IODevice>>,
pub void: u32,
pub dustbin: u32,
}
#[expect(dead_code)]
fn log(message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}");
}
@@ -31,7 +32,7 @@ impl Processor {
registers: RegFile::default(),
halted: false,
io_devices,
void: 0,
dustbin: 0,
}
}
@@ -49,16 +50,16 @@ impl Processor {
self.halted = false;
// Get value from PCX.
let addr = self.fetch()?;
let addr = self.fetch();
// Increment PCX.
self.advance();
// Set MAR to the previous value of PCX.
*self.reg(Register::Mar)? = addr;
*self.reg(Register::Mar) = addr;
let val = self.memory.read_word(addr)?;
// Set CIR to the value of RAM[MAR].
*self.reg(Register::Mar)? = val;
*self.reg(Register::Mar) = val;
// Decode and execute the instruction.
let instruction = Instruction::decode(val)
@@ -68,17 +69,18 @@ impl Processor {
Ok((addr, instruction))
}
const fn fetch(&self) -> Result<u32, ProcessorError> {
fn fetch(&self) -> u32 {
self.get(Register::Pcx)
}
pub const fn get(&self, reg: Register) -> Result<u32, ProcessorError> {
#[must_use]
pub fn get(&self, reg: Register) -> u32 {
self.registers.get(reg)
}
pub const fn reg(&mut self, reg: Register) -> Result<&mut u32, ProcessorError> {
pub fn reg(&mut self, reg: Register) -> &mut u32 {
match reg {
Register::Zero => Ok(&mut self.void),
Register::Zero => &mut self.dustbin,
_ => self.registers.reg(reg),
}
}
@@ -96,71 +98,38 @@ impl Processor {
// functions to set new state
fn set_flag(&mut self, flag: Flag, value: bool) {
if value {
*self
.reg(Register::Sts)
.expect("STS should never be invalid") |= flag as u32;
*self.reg(Register::Sts) |= flag as u32;
} else {
*self
.reg(Register::Sts)
.expect("STS should never be invalid") &= !(flag as u32);
*self.reg(Register::Sts) &= !(flag as u32);
}
}
fn get_flag(&self, flag: Flag) -> Result<bool, ProcessorError> {
Ok(self.get(Register::Sts)? & (flag as u32) != 0)
fn get_flag(&self, flag: Flag) -> bool {
self.get(Register::Sts) & (flag as u32) != 0
}
fn advance(&mut self) -> Result<(), ProcessorError> {
fn advance(&mut self) {
// increment PCX
*self.reg(Register::Pcx)? += 4;
Ok(())
*self.reg(Register::Pcx) += 4;
}
fn jump(&mut self, reg: Register, offset: u16) -> Result<(), ProcessorError> {
*self.reg(Register::Pcx)? = self.get(reg)? + u32::from(offset);
Ok(())
fn jump(&mut self, reg: Register, offset: u16) {
*self.reg(Register::Pcx) = self.get(reg) + u32::from(offset);
}
pub fn begin_interrupt(
&mut self,
interrupt: Interrupt,
) -> Result<(), ProcessorError> {
let idt = self.get(Register::Idr)?;
let addr = self
.memory
.read_word(idt + u32::from(interrupt.as_u8()) * 4)?;
println!("INFO: Interrupt {interrupt:?} addr: {addr}");
self.push(self.get(Register::Pcx)?)?;
*self.reg(Register::Pcx)? = addr;
Ok(())
}
fn push(&mut self, val: u32) -> Result<(), ProcessorError> {
*self.reg(Register::Spr)? -= 4;
let reg = *self.reg(Register::Spr)?;
self.memory.write_word(reg, val)
}
fn pop(&mut self) -> Result<u32, ProcessorError> {
let reg = *self.reg(Register::Spr)?;
let val = self.memory.read_word(reg)?;
*self.reg(Register::Spr)? += 4;
Ok(val)
pub fn begin_interrupt(&mut self, _int: Interrupt) {
// first we get the address of the interrupt descriptor table.
todo!();
}
// TODO: remove this once implemented
#[allow(clippy::needless_pass_by_ref_mut)]
fn end_interrupt(&mut self) -> Result<(), ProcessorError> {
let ret = self.pop()?;
*self.reg(Register::Ret)? = ret;
*self.reg(Register::Pcx)? = ret;
Ok(())
fn end_interrupt(&mut self) {
todo!();
}
pub fn get_stack(&mut self, n: u32) -> Result<Vec<u8>, ProcessorError> {
let addr = self.get(Register::Spr)?;
let addr = self.get(Register::Spr);
let size = n * 4;
// returns the stack
self.memory.read_range(
@@ -195,30 +164,30 @@ impl Executable for Instruction {
// No operation - a blank line.
// Copies from SrcReg to a.drReg.
Self::Mov(a) => {
*cpu.reg(a.dr)? = cpu.get(a.sr1)?;
*cpu.reg(a.dr) = cpu.get(a.sr1);
}
// Copies from SrcReg to a.drReg, sign extending the value to take up a full
// word.
Self::MovSigned(a) => {
*cpu.reg(a.dr)? = sign_extend(cpu.get(a.sr1)?);
*cpu.reg(a.dr) = sign_extend(cpu.get(a.sr1));
}
// Loads a byte from memory address (base + offset) into a.drReg. The
// effective address must be byte-aligned.
Self::LoadByte(a) => {
*cpu.reg(a.r2)? = u32::from(
*cpu.reg(a.r2) = u32::from(
cpu.memory
.read_byte(cpu.get(a.r1)? + u32::from(a.immediate))?,
.read_byte(cpu.get(a.r1) + u32::from(a.immediate))?,
);
}
// Loads a sign-extended byte from memory address (base + offset) into
// a.drReg. The effective address must be byte-aligned.
Self::LoadByteSigned(a) => {
*cpu.reg(a.r2)? = sign_extend(u32::from(
*cpu.reg(a.r2) = sign_extend(u32::from(
cpu.memory
.read_byte(cpu.get(a.r1)? + u32::from(a.immediate))?,
.read_byte(cpu.get(a.r1) + u32::from(a.immediate))?,
));
}
@@ -227,18 +196,18 @@ impl Executable for Instruction {
Self::LoadHalfword(a) => {
// we read an entire word, then right shift so we only get the first half
// of the word
*cpu.reg(a.r2)? = cpu
*cpu.reg(a.r2) = cpu
.memory
.read_word(cpu.get(a.r1)? + u32::from(a.immediate))?
.read_word(cpu.get(a.r1) + u32::from(a.immediate))?
>> 16;
}
// Loads a sign-extended half-word from memory address (base + offset) into
// a.drReg. The effective address must be 2-byte-aligned.
Self::LoadHalfwordSigned(a) => {
*cpu.reg(a.r2)? = sign_extend(
*cpu.reg(a.r2) = sign_extend(
cpu.memory
.read_word(cpu.get(a.r1)? + u32::from(a.immediate))?
.read_word(cpu.get(a.r1) + u32::from(a.immediate))?
>> 16,
);
}
@@ -246,17 +215,17 @@ impl Executable for Instruction {
// Loads a word from memory address (base + offset) into a.drReg. The
// effective address must be 4-byte-aligned.
Self::LoadWord(a) => {
*cpu.reg(a.r2)? = cpu
*cpu.reg(a.r2) = cpu
.memory
.read_word(cpu.get(a.r1)? + u32::from(a.immediate))?;
.read_word(cpu.get(a.r1) + u32::from(a.immediate))?;
}
// Stores a byte from SrcReg in memory address (base + offset) The effective
// address must be byte-aligned.
Self::StoreByte(a) => {
cpu.memory.write_byte(
cpu.get(a.r2)? + u32::from(a.immediate),
cpu.get(a.r1)? as u8,
cpu.get(a.r2) + u32::from(a.immediate),
cpu.get(a.r1) as u8,
)?;
}
@@ -264,147 +233,149 @@ impl Executable for Instruction {
// effective address must be 2-byte-aligned.
Self::StoreHalfword(a) => {
// split the value into bytes and then write two bytes
let bytes = (cpu.get(a.r1)? as u16).to_le_bytes();
let bytes = (cpu.get(a.r1) as u16).to_le_bytes();
cpu.memory
.write_byte(cpu.get(a.r2)? + u32::from(a.immediate), bytes[0])?;
.write_byte(cpu.get(a.r2) + u32::from(a.immediate), bytes[0])?;
cpu.memory
.write_byte(cpu.get(a.r2)? + u32::from(a.immediate) + 1, bytes[1])?;
.write_byte(cpu.get(a.r2) + u32::from(a.immediate) + 1, bytes[1])?;
}
// Stores a word from SrcReg in memory address (base + offset) The effective
// address must be 4-byte-aligned.
Self::StoreWord(a) => {
cpu.memory.write_word(
cpu.get(a.r2)? + u32::from(a.immediate),
cpu.get(a.r1)?,
)?;
cpu.memory
.write_word(cpu.get(a.r2) + u32::from(a.immediate), cpu.get(a.r1))?;
}
// Loads a 16-bit literal value into reg, setting the bottom 16 bits of the
// word. To populate the upper 16 bits, see LUI.
Self::LoadLowerImmediate(a) => {
*cpu.reg(a.r1)? = u32::from(a.immediate);
*cpu.reg(a.r1) = u32::from(a.immediate);
}
// Loads a 16-bit literal value into reg, setting the top 16 bits of the word.
// To populate the lower 16 bits, see LLI.
Self::LoadUpperImmediate(a) => {
*cpu.reg(a.r1)? =
(cpu.get(a.r1)? & 0x0000_FFFF) | (u32::from(a.immediate) << 16);
*cpu.reg(a.r1) =
(cpu.get(a.r1) & 0x0000_FFFF) | (u32::from(a.immediate) << 16);
}
// Unconditionally jumps to the calculated address or direct address
Self::Jump(a) => cpu.jump(a.r1, a.immediate)?,
Self::Jump(a) => cpu.jump(a.r1, a.immediate),
// Jumps to the calculated address or direct address if equal flag set.
Self::JumpEq(a) => {
if cpu.get_flag(Flag::Equal)? {
cpu.jump(a.r1, a.immediate)?;
if cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate);
}
}
// Jumps to the calculated address or direct address if equal flag not set.
Self::JumpNeq(a) => {
if !cpu.get_flag(Flag::Equal)? {
cpu.jump(a.r1, a.immediate)?;
if !cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate);
}
}
// Jumps to the calculated address or direct address if greater than flag set.
Self::JumpGt(a) => {
if cpu.get_flag(Flag::GreaterThan)? {
cpu.jump(a.r1, a.immediate)?;
if cpu.get_flag(Flag::GreaterThan) {
cpu.jump(a.r1, a.immediate);
}
}
// Jumps to the calculated address or direct address if greater than flag or
// equal flag set.
Self::JumpGe(a) => {
if cpu.get_flag(Flag::GreaterThan)? || cpu.get_flag(Flag::Equal)? {
cpu.jump(a.r1, a.immediate)?;
if cpu.get_flag(Flag::GreaterThan) || cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate);
}
}
// Jumps to the calculated address or direct address if less than flag set.
Self::JumpLt(a) => {
if cpu.get_flag(Flag::LessThan)? {
cpu.jump(a.r1, a.immediate)?;
if cpu.get_flag(Flag::LessThan) {
cpu.jump(a.r1, a.immediate);
}
}
// Jumps to the calculated address or direct address if less than flag or
// equal flag set.
Self::JumpLe(a) => {
if cpu.get_flag(Flag::LessThan)? || cpu.get_flag(Flag::Equal)? {
cpu.jump(a.r1, a.immediate)?;
if cpu.get_flag(Flag::LessThan) || cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate);
}
}
// Increments the value in the given register
Self::Increment(a) => *cpu.reg(a.sr1)? = inc(cpu.get(a.sr1)?),
Self::Increment(a) => *cpu.reg(a.sr1) = inc(cpu.get(a.sr1)),
// Decrements the value in the given register
Self::Decrement(a) => *cpu.reg(a.sr1)? = dec(cpu.get(a.sr1)?),
Self::Decrement(a) => *cpu.reg(a.sr1) = dec(cpu.get(a.sr1)),
// Left shifts the value in Reg by the given amount (either a register, or a
// literal value)
Self::ShiftLeft(a) => {
let reg = cpu.get(a.sr1)?;
let val = a.shamt;
*cpu.reg(a.sr1)? = shl(reg, val);
let regval = cpu.get(a.sr2);
let val = cpu.get(a.sr1);
*cpu.reg(a.sr1) =
shl(val, if regval != 0 { regval as u8 } else { a.shamt });
}
// Right shifts the value in Reg by the given amount (either a register, or a
// literal value).
Self::ShiftRight(a) => {
let regval = cpu.get(a.sr1)?;
let val = a.shamt;
*cpu.reg(a.sr1)? = shr(regval, val);
let regval = cpu.get(a.sr2);
let val = cpu.get(a.sr1);
*cpu.reg(a.sr1) =
shr(val, if regval != 0 { regval as u8 } else { a.shamt });
}
// Adds the value of Src2 to Src1 and writes the result to a.dr
Self::Add(a) => {
*cpu.reg(a.dr)? = add(cpu.get(a.sr1)?, cpu.get(a.sr2)?);
*cpu.reg(a.dr) = add(cpu.get(a.sr1), cpu.get(a.sr2));
}
// Subtracts the value of Src2 from Src1 and writes the result to a.dr
Self::Sub(a) => {
*cpu.reg(a.dr)? = sub(cpu.get(a.sr1)?, cpu.get(a.sr2)?);
*cpu.reg(a.dr) = sub(cpu.get(a.sr1), cpu.get(a.sr2));
}
Self::AddImmediate(a) => {
*cpu.reg(a.r2)? = add(cpu.get(a.r1)?, u32::from(a.immediate));
*cpu.reg(a.r2) = add(cpu.get(a.r1), u32::from(a.immediate));
}
Self::SubImmediate(a) => {
*cpu.reg(a.r2)? = sub(cpu.get(a.r1)?, u32::from(a.immediate));
*cpu.reg(a.r2) = sub(cpu.get(a.r1), u32::from(a.immediate));
}
// Performs bitwise AND on Src1 and Src2 storing the result in a.dr
Self::And(a) => *cpu.reg(a.dr)? = and(cpu.get(a.sr1)?, cpu.get(a.sr2)?),
Self::And(a) => *cpu.reg(a.dr) = and(cpu.get(a.sr1), cpu.get(a.sr2)),
// Performs bitwise OR on Src1 and Src2 storing the result in a.dr
Self::Or(a) => *cpu.reg(a.dr)? = or(cpu.get(a.sr1)?, cpu.get(a.sr2)?),
Self::Or(a) => *cpu.reg(a.dr) = or(cpu.get(a.sr1), cpu.get(a.sr2)),
// Performs bitwise NOT on Src storing the result in a.dr
Self::Not(a) => *cpu.reg(a.dr)? = not(cpu.get(a.sr1)?),
Self::Not(a) => *cpu.reg(a.dr) = not(cpu.get(a.sr1)),
// Performs bitwise XOR on Src1 and Src2 storing the result in a.dr
Self::Xor(a) => *cpu.reg(a.dr)? = xor(cpu.get(a.sr1)?, cpu.get(a.sr2)?),
Self::Xor(a) => *cpu.reg(a.dr) = xor(cpu.get(a.sr1), cpu.get(a.sr2)),
// Performs bitwise NAND on Src1 and Src2 storing the result in a.dr
Self::Nand(a) => *cpu.reg(a.dr)? = nand(cpu.get(a.sr1)?, cpu.get(a.sr2)?),
Self::Nand(a) => *cpu.reg(a.dr) = nand(cpu.get(a.sr1), cpu.get(a.sr2)),
// Performs bitwise NOR on Src1 and Src2 storing the result in a.dr
Self::Nor(a) => *cpu.reg(a.dr)? = nor(cpu.get(a.sr1)?, cpu.get(a.sr2)?),
Self::Nor(a) => *cpu.reg(a.dr) = nor(cpu.get(a.sr1), cpu.get(a.sr2)),
// Performs bitwise XNOR on Src1 and Src2 storing the result in a.dr
Self::Xnor(a) => *cpu.reg(a.dr)? = xnor(cpu.get(a.sr1)?, cpu.get(a.sr2)?),
Self::Xnor(a) => *cpu.reg(a.dr) = xnor(cpu.get(a.sr1), cpu.get(a.sr2)),
// Compares the value of Reg1 to the value in Reg2. The results of the
// comparisons are set in the Status register.
Self::Compare(a) => {
cpu.cmp(cpu.get(a.sr1)?, cpu.get(a.sr2)?);
cpu.cmp(cpu.get(a.sr1), cpu.get(a.sr2));
}
// Initiates an interrupt with the given 8 bit interrupt code.
@@ -412,12 +383,12 @@ impl Executable for Instruction {
// - The return address is saved to the RET register.
// - The stack base ptr is set to the kernel stack.
Self::Interrupt(interrupt_code) => {
cpu.begin_interrupt(interrupt_code)?;
cpu.begin_interrupt(interrupt_code);
}
// Returns from an interrupt,
Self::IntReturn => {
cpu.end_interrupt()?;
cpu.end_interrupt();
}
// Halts the processor.
+97 -197
View File
@@ -18,27 +18,19 @@ fn test_nop_instruction() {
);
assert_eq!(
cpu.registers
.get(Register::Rg0)
.expect("Failed to get register Rg0"),
initial_state
.get(Register::Rg0)
.expect("Failed to get register Rg0")
cpu.registers.get(Register::Rg0),
initial_state.get(Register::Rg0)
);
assert_eq!(
cpu.registers
.get(Register::Acc)
.expect("Failed to get register Acc"),
initial_state
.get(Register::Acc)
.expect("Failed to get register Acc")
cpu.registers.get(Register::Acc),
initial_state.get(Register::Acc)
);
}
#[test]
fn test_mov_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1234_5678;
*cpu.reg(Register::Rg1) = 0x1234_5678;
let mov_instr = Instruction::Mov(RTypeArgs::new(
Some(Register::Rg1),
@@ -50,16 +42,13 @@ fn test_mov_instruction() {
mov_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0x1234_5678
);
assert_eq!(cpu.get(Register::Rg2), 0x1234_5678);
}
#[test]
fn test_mov_signed_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x0000_00FF;
*cpu.reg(Register::Rg1) = 0x0000_00FF;
let mov_signed_instr = Instruction::MovSigned(RTypeArgs::new(
Some(Register::Rg1),
@@ -71,10 +60,7 @@ fn test_mov_signed_instruction() {
mov_signed_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0xFFFF_FFFF
);
assert_eq!(cpu.get(Register::Rg2), 0xFFFF_FFFF);
}
#[test]
@@ -84,7 +70,7 @@ fn test_load_byte_instruction() {
cpu.memory
.write_byte(addr, 0xAB)
.expect("Failed to write byte to memory");
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr - 4;
*cpu.reg(Register::Rg1) = addr - 4;
let load_byte_instr = Instruction::LoadByte(ITypeArgs::new(
4,
@@ -95,10 +81,7 @@ fn test_load_byte_instruction() {
load_byte_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0x0000_00AB
);
assert_eq!(cpu.get(Register::Rg2), 0x0000_00AB);
}
#[test]
@@ -108,7 +91,7 @@ fn test_load_byte_signed_instruction() {
cpu.memory
.write_byte(addr, 0xFF)
.expect("Failed to write byte to memory");
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
*cpu.reg(Register::Rg1) = addr;
let load_byte_signed_instr = Instruction::LoadByteSigned(ITypeArgs::new(
0,
@@ -119,10 +102,7 @@ fn test_load_byte_signed_instruction() {
load_byte_signed_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0xFFFF_FFFF
);
assert_eq!(cpu.get(Register::Rg2), 0xFFFF_FFFF);
}
#[test]
@@ -132,7 +112,7 @@ fn test_load_halfword_instruction() {
cpu.memory
.write_word(addr, 0x1234_5678)
.expect("Failed to write word to memory");
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
*cpu.reg(Register::Rg1) = addr;
let load_halfword_instr = Instruction::LoadHalfword(ITypeArgs::new(
0,
@@ -143,10 +123,7 @@ fn test_load_halfword_instruction() {
load_halfword_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0x0000_1234
);
assert_eq!(cpu.get(Register::Rg2), 0x0000_1234);
}
#[test]
@@ -156,7 +133,7 @@ fn test_load_word_instruction() {
cpu.memory
.write_word(addr, 0x1234_5678)
.expect("Failed to write word to memory");
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
*cpu.reg(Register::Rg1) = addr;
let load_word_instr = Instruction::LoadWord(ITypeArgs::new(
0,
@@ -167,18 +144,15 @@ fn test_load_word_instruction() {
load_word_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0x1234_5678
);
assert_eq!(cpu.get(Register::Rg2), 0x1234_5678);
}
#[test]
fn test_store_byte_instruction() {
let mut cpu = create_test_processor();
let addr = 0x100;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0xAB;
*cpu.reg(Register::Rg1) = addr;
*cpu.reg(Register::Rg2) = 0xAB;
let store_byte_instr = Instruction::StoreByte(ITypeArgs::new(
0,
@@ -196,8 +170,8 @@ fn test_store_byte_instruction() {
fn test_store_word_instruction() {
let mut cpu = create_test_processor();
let addr = 0x100;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0x1234_5678;
*cpu.reg(Register::Rg1) = addr;
*cpu.reg(Register::Rg2) = 0x1234_5678;
let store_word_instr = Instruction::StoreWord(ITypeArgs::new(
0,
@@ -214,8 +188,8 @@ fn test_store_word_instruction() {
#[test]
fn test_add_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 15;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 25;
*cpu.reg(Register::Rg1) = 15;
*cpu.reg(Register::Rg2) = 25;
let add_instr = Instruction::Add(RTypeArgs::new(
Some(Register::Rg1),
@@ -227,17 +201,14 @@ fn test_add_instruction() {
add_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
40
);
assert_eq!(cpu.get(Register::Rg3), 40);
}
#[test]
fn test_sub_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 50;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 20;
*cpu.reg(Register::Rg1) = 50;
*cpu.reg(Register::Rg2) = 20;
let sub_instr = Instruction::Sub(RTypeArgs::new(
Some(Register::Rg1),
@@ -249,17 +220,14 @@ fn test_sub_instruction() {
sub_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
30
);
assert_eq!(cpu.get(Register::Rg3), 30);
}
#[test]
fn test_and_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
let and_instr = Instruction::And(RTypeArgs::new(
Some(Register::Rg1),
@@ -271,17 +239,14 @@ fn test_and_instruction() {
and_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
0b1000
);
assert_eq!(cpu.get(Register::Rg3), 0b1000);
}
#[test]
fn test_or_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
let or_instr = Instruction::Or(RTypeArgs::new(
Some(Register::Rg1),
@@ -293,17 +258,14 @@ fn test_or_instruction() {
or_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
0b1110
);
assert_eq!(cpu.get(Register::Rg3), 0b1110);
}
#[test]
fn test_xor_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
let xor_instr = Instruction::Xor(RTypeArgs::new(
Some(Register::Rg1),
@@ -315,16 +277,13 @@ fn test_xor_instruction() {
xor_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
0b0110
);
assert_eq!(cpu.get(Register::Rg3), 0b0110);
}
#[test]
fn test_not_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x0F0F_0F0F;
*cpu.reg(Register::Rg1) = 0x0F0F_0F0F;
let not_instr = Instruction::Not(RTypeArgs::new(
Some(Register::Rg1),
@@ -336,17 +295,14 @@ fn test_not_instruction() {
not_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0xF0F0_F0F0
);
assert_eq!(cpu.get(Register::Rg2), 0xF0F0_F0F0);
}
#[test]
fn test_compare_equal() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 42;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 42;
*cpu.reg(Register::Rg1) = 42;
*cpu.reg(Register::Rg2) = 42;
let cmp_instr = Instruction::Compare(RTypeArgs::new(
Some(Register::Rg1),
@@ -359,22 +315,16 @@ fn test_compare_equal() {
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert!(cpu.get_flag(Flag::Equal).expect("Failed to get flag Equal"));
assert!(
!cpu.get_flag(Flag::GreaterThan)
.expect("Failed to get flag GreaterThan")
);
assert!(
!cpu.get_flag(Flag::LessThan)
.expect("Failed to get flag LessThan")
);
assert!(cpu.get_flag(Flag::Equal));
assert!(!cpu.get_flag(Flag::GreaterThan));
assert!(!cpu.get_flag(Flag::LessThan));
}
#[test]
fn test_compare_greater_than() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 50;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 30;
*cpu.reg(Register::Rg1) = 50;
*cpu.reg(Register::Rg2) = 30;
let cmp_instr = Instruction::Compare(RTypeArgs::new(
Some(Register::Rg1),
@@ -387,22 +337,16 @@ fn test_compare_greater_than() {
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert!(!cpu.get_flag(Flag::Equal).expect("Failed to get flag Equal"));
assert!(
cpu.get_flag(Flag::GreaterThan)
.expect("Failed to get flag GreaterThan")
);
assert!(
!cpu.get_flag(Flag::LessThan)
.expect("Failed to get flag LessThan")
);
assert!(!cpu.get_flag(Flag::Equal));
assert!(cpu.get_flag(Flag::GreaterThan));
assert!(!cpu.get_flag(Flag::LessThan));
}
#[test]
fn test_compare_less_than() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 20;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 30;
*cpu.reg(Register::Rg1) = 20;
*cpu.reg(Register::Rg2) = 30;
let cmp_instr = Instruction::Compare(RTypeArgs::new(
Some(Register::Rg1),
@@ -415,21 +359,15 @@ fn test_compare_less_than() {
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert!(!cpu.get_flag(Flag::Equal).expect("Failed to get flag Equal"));
assert!(
!cpu.get_flag(Flag::GreaterThan)
.expect("Failed to get flag GreaterThan")
);
assert!(
cpu.get_flag(Flag::LessThan)
.expect("Failed to get flag LessThan")
);
assert!(!cpu.get_flag(Flag::Equal));
assert!(!cpu.get_flag(Flag::GreaterThan));
assert!(cpu.get_flag(Flag::LessThan));
}
#[test]
fn test_increment_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 42;
*cpu.reg(Register::Rg1) = 42;
let inc_instr =
Instruction::Increment(RTypeArgs::new(Some(Register::Rg1), None, None, None));
@@ -437,16 +375,13 @@ fn test_increment_instruction() {
inc_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
43
);
assert_eq!(cpu.get(Register::Rg1), 43);
}
#[test]
fn test_decrement_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 42;
*cpu.reg(Register::Rg1) = 42;
let dec_instr =
Instruction::Decrement(RTypeArgs::new(Some(Register::Rg1), None, None, None));
@@ -454,16 +389,13 @@ fn test_decrement_instruction() {
dec_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
41
);
assert_eq!(cpu.get(Register::Rg1), 41);
}
#[test]
fn test_shift_left_with_shamt() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1010;
*cpu.reg(Register::Rg1) = 0b1010;
let shl_instr = Instruction::ShiftLeft(RTypeArgs::new(
Some(Register::Rg1),
@@ -475,16 +407,13 @@ fn test_shift_left_with_shamt() {
shl_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
0b10_1000
);
assert_eq!(cpu.get(Register::Rg1), 0b10_1000);
}
#[test]
fn test_shift_right_with_shamt() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b10_1000;
*cpu.reg(Register::Rg1) = 0b10_1000;
let shr_instr = Instruction::ShiftRight(RTypeArgs::new(
Some(Register::Rg1),
@@ -496,29 +425,27 @@ fn test_shift_right_with_shamt() {
shr_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
0b1010
);
assert_eq!(cpu.get(Register::Rg1), 0b1010);
}
// #[test]
// fn test_shift_left_with_register() {
// let mut cpu = create_test_processor();
// *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1010;
#[test]
fn test_shift_left_with_register() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1) = 0b1010;
*cpu.reg(Register::Rg2) = 3;
// let shl_instr =
// Instruction::ShiftLeft(RTypeArgs::new(Some(Register::Rg1), None, None,
// Some(3)));
let shl_instr = Instruction::ShiftLeft(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Rg2),
None,
None,
));
// shl_instr.execute(&mut cpu).expect(
// "Emulator was slain by losing the game while attempting to execute
// instruction", );
// assert_eq!(
// cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
// 0b101_0000
// );
// }
shl_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg1), 0b101_0000);
}
#[test]
fn test_load_lower_immediate() {
@@ -533,16 +460,13 @@ fn test_load_lower_immediate() {
lli_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
0x0000_1234
);
assert_eq!(cpu.get(Register::Rg1), 0x0000_1234);
}
#[test]
fn test_load_upper_immediate() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x0000_5678;
*cpu.reg(Register::Rg1) = 0x0000_5678;
let lui_instr = Instruction::LoadUpperImmediate(ITypeArgs::new(
0x1234,
@@ -553,38 +477,29 @@ fn test_load_upper_immediate() {
lui_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
0x1234_5678
);
assert_eq!(cpu.get(Register::Rg1), 0x1234_5678);
}
#[test]
fn test_jump_unconditional() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1000;
let initial_pc = cpu.get(Register::Pcx).expect("Failed to get register Pcx");
*cpu.reg(Register::Rg1) = 0x1000;
let initial_pc = cpu.get(Register::Pcx);
let jump_instr = Instruction::Jump(ITypeArgs::new(0x100, Some(Register::Rg1), None));
jump_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
0x1100
);
assert_ne!(
cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
initial_pc
);
assert_eq!(cpu.get(Register::Pcx), 0x1100);
assert_ne!(cpu.get(Register::Pcx), initial_pc);
}
#[test]
fn test_jump_equal_when_flag_set() {
let mut cpu = create_test_processor();
cpu.set_flag(Flag::Equal, true);
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1000;
*cpu.reg(Register::Rg1) = 0x1000;
let jump_eq_instr =
Instruction::JumpEq(ITypeArgs::new(0x100, Some(Register::Rg1), None));
@@ -592,18 +507,15 @@ fn test_jump_equal_when_flag_set() {
jump_eq_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
0x1100
);
assert_eq!(cpu.get(Register::Pcx), 0x1100);
}
#[test]
fn test_jump_equal_when_flag_not_set() {
let mut cpu = create_test_processor();
cpu.set_flag(Flag::Equal, false);
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1000;
let initial_pc = cpu.get(Register::Pcx).expect("Failed to get register Pcx");
*cpu.reg(Register::Rg1) = 0x1000;
let initial_pc = cpu.get(Register::Pcx);
let jump_eq_instr =
Instruction::JumpEq(ITypeArgs::new(0x100, Some(Register::Rg1), None));
@@ -611,10 +523,7 @@ fn test_jump_equal_when_flag_not_set() {
jump_eq_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
initial_pc
);
assert_eq!(cpu.get(Register::Pcx), initial_pc);
}
#[test]
@@ -631,8 +540,8 @@ fn test_halt_instruction() {
#[test]
fn test_nand_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
let nand_instr = Instruction::Nand(RTypeArgs::new(
Some(Register::Rg1),
@@ -644,17 +553,14 @@ fn test_nand_instruction() {
nand_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
!0b1000
);
assert_eq!(cpu.get(Register::Rg3), !0b1000);
}
#[test]
fn test_nor_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
let nor_instr = Instruction::Nor(RTypeArgs::new(
Some(Register::Rg1),
@@ -666,17 +572,14 @@ fn test_nor_instruction() {
nor_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
!0b1110
);
assert_eq!(cpu.get(Register::Rg3), !0b1110);
}
#[test]
fn test_xnor_instruction() {
let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
*cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010;
let xnor_instr = Instruction::Xnor(RTypeArgs::new(
Some(Register::Rg1),
@@ -688,8 +591,5 @@ fn test_xnor_instruction() {
xnor_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
!0b0110
);
assert_eq!(cpu.get(Register::Rg3), !0b0110);
}
+2 -10
View File
@@ -133,21 +133,13 @@ impl Component for ControlPanel {
}
));
let pcx = state
.reg_file
.get(Register::Pcx)
.expect("PCX should never be invalid");
let pcx = state.reg_file.get(Register::Pcx);
let instructions = state.instructions;
ui.label(format!("Instructions: {instructions}"));
ui.label(format!("PC: 0x{pcx:08X}"));
let instruction = Instruction::decode(
state
.reg_file
.get(Register::Cir)
.expect("CIR should never be invalid"),
)
let instruction = Instruction::decode(state.reg_file.get(Register::Cir))
.map_or_else(
|_| "Invalid Instruction".to_string(),
|instruction| instruction.to_string(),
+2 -24
View File
@@ -5,6 +5,7 @@ use std::{
path::{Path, PathBuf},
};
use assembler::compiler_engine::CompilerEngine;
use common::prelude::Instruction;
use egui::{Align, Context, Key, Layout, Ui};
@@ -16,7 +17,7 @@ use crate::emulator::{
ui::interface::Component,
};
use assembler::prelude::*;
// use assembler::prelude::*;
#[derive(Default)]
pub struct Editor {
@@ -451,29 +452,6 @@ impl Editor {
.flat_map(|i| i.encode().to_be_bytes().to_vec())
.collect();
}
Some("dsc") => {
let output_path = Path::new(path).with_extension("dsa");
if let Err(e) = compiler::compile_file(path, &output_path) {
self.error = Some(format!("Compiler error: {}", e));
}
let mut compiler = CompilerEngine::new();
compiler.start_compilation(&output_path);
// Or block until done
let instructions = match compiler.wait_for_result() {
Ok(instructions) => instructions,
Err(e) => {
self.error = Some(format!("Assembler error: {}", e));
return;
}
};
self.output = instructions
.iter()
.flat_map(|i| i.encode().to_be_bytes().to_vec())
.collect();
}
Some("dsb") => {
if let Ok(bytes) = fs::read(path) {
self.output = bytes;
+4 -3
View File
@@ -51,6 +51,7 @@ impl Component for StackInspector {
ui.label("Address");
ui.label("Value");
ui.end_row();
for (i, value) in
state.stack_view.chunks(4).take(32).enumerate()
{
@@ -58,9 +59,9 @@ impl Component for StackInspector {
"Could not read 4 byte instruction or data! Something is wrong.",
));
ui.label(format!(
"+{} [{}]",
i*4,
state.reg_file.get(Register::Spr).expect("SPR should never be invalid") + i as u32 * 4
"{} [{}]",
i,
state.reg_file.get(Register::Spr) - i as u32 * 4
));
ui.label(format!("0x{value:08X} ({value})"));
ui.end_row();
+279
View File
@@ -0,0 +1,279 @@
```rust
// src/assembler/source.rs
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SourcePosition {
pub line: u32,
pub column: u32,
pub offset: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceSpan {
pub start: SourcePosition,
pub end: SourcePosition,
pub file_id: u64, // Hash of the file path
}
impl SourceSpan {
pub fn new(start: SourcePosition, end: SourcePosition, file_id: u64) -> Self {
Self { start, end, file_id }
}
pub fn single_char(pos: SourcePosition, file_id: u64) -> Self {
Self {
start: pos,
end: pos,
file_id,
}
}
}
2. Enhanced Token with Source Information
Update the Token type to include source positions:
```rust
// src/assembler/model.rs
pub struct Token {
pub kind: TokenKind,
pub span: SourceSpan,
pub raw: String, // Original source text
}
pub enum TokenKind {
// ... existing variants ...
}
3. Enhanced CodeModule Structure
Enhance the
CodeModule
struct to track source information:
```rust
// src/assembler/mod.rs
pub struct CodeModule {
pub path: PathBuf,
pub hash: u64,
pub source: String,
pub lines: Vec<usize>, // Line start offsets for quick lookup
pub tokens: Vec<Token>,
pub nodes: Vec<Node>,
pub dependencies: Vec<CodeModule>,
}
impl CodeModule {
pub fn new(path: PathBuf, source: String) -> Self {
let hash = quick_hash(&path);
let lines = source.lines()
.scan(0, |offset, line| {
let start = *offset;
*offset += line.len() + 1; // +1 for newline
Some(start)
})
.collect();
Self {
path,
hash,
source,
lines,
tokens: Vec::new(),
nodes: Vec::new(),
dependencies: Vec::new(),
}
}
pub fn position_from_offset(&self, offset: usize) -> (u32, u32) {
match self.lines.binary_search(&offset) {
Ok(line) => (line as u32 + 1, 1),
Err(0) => (1, offset as u32 + 1),
Err(line) => {
let line_start = self.lines[line - 1];
(line as u32, (offset - line_start + 1) as u32)
}
}
}
}
4. Enhanced Lexer with Source Positions
Update the lexer to track source positions:
```rust
// src/assembler/lexer.rs
pub fn lex(module: &mut CodeModule) -> Result<(), AssembleError> {
let source = &module.source;
let mut tokens = Vec::new();
let mut pos = 0;
let mut line_start = 0;
let mut line = 1;
while pos < source.len() {
let c = source[pos..].chars().next().unwrap();
if c == '\n' {
line += 1;
line_start = pos + 1;
pos += 1;
continue;
}
if c.is_whitespace() {
pos += 1;
continue;
}
let token_start = pos;
// ... existing token parsing logic ...
// When creating a token:
let start_pos = SourcePosition {
line,
column: (token_start - line_start + 1) as u32,
offset: token_start,
};
// Update pos based on token length
let token_length = /* calculate token length */;
pos += token_length;
let end_pos = SourcePosition {
line,
column: (pos - line_start + 1) as u32,
offset: pos,
};
tokens.push(Token {
kind: token_kind,
span: SourceSpan::new(start_pos, end_pos, module.hash),
raw: source[token_start..pos].to_string(),
});
}
module.tokens = tokens;
Ok(())
}
5. Enhanced Error Reporting
Create a structured error type with source context:
```rust
// src/assembler/error.rs
#[derive(Debug)]
pub struct AssemblerError {
pub kind: ErrorKind,
pub span: SourceSpan,
pub message: String,
pub context: Vec<String>,
}
impl AssemblerError {
pub fn new(kind: ErrorKind, span: SourceSpan, message: impl Into<String>) -> Self {
Self {
kind,
span,
message: message.into(),
context: Vec::new(),
}
}
pub fn with_context(mut self, context: impl Into<String>) -> Self {
self.context.push(context.into());
self
}
pub fn format(&self, module: &CodeModule) -> String {
let (line, col) = module.position_from_offset(self.span.start.offset);
let line_content = module.source.lines().nth(line as usize - 1).unwrap_or("");
let mut output = format!(
"{}:{}:{}: {}\n",
module.path.display(),
line,
col,
self.message
);
// Add source line with caret
output.push_str(&format!("{}\n", line_content));
output.push_str(&" ".repeat(col as usize - 1));
output.push_str("^\n");
// Add context if any
for ctx in &self.context {
output.push_str(&format!(" = note: {}\n", ctx));
}
output
}
}
6. Integration with Compilation Pipeline
Update the compilation pipeline to use the enhanced types:
```rust
// src/assembler/mod.rs
pub fn assemble(src: &Path) -> Result<Vec<Instruction>, AssemblerError> {
let source = std::fs::read_to_string(src)
.map_err(|e| AssemblerError::io_error(src, e))?;
let mut module = CodeModule::new(src.to_path_buf(), source);
// Lexing
lexer::lex(&mut module)?;
// Parsing
parser::parse(&mut module)?;
// Resolution
resolver::resolve(&mut module)?;
// Code generation
codegen::generate(&module)
}
7. Logging Integration
Enhance the logging system to include source context:
```rust
// src/util/logging.rs
pub trait Loggable {
fn log(&self, level: LogLevel, message: impl std::fmt::Display);
fn log_with_span(&self, level: LogLevel, span: &SourceSpan, message: impl std::fmt::Display);
}
impl Loggable for CodeModule {
fn log_with_span(&self, level: LogLevel, span: &SourceSpan, message: impl std::fmt::Display) {
if span.file_id != self.hash {
if let Some(dep) = self.find_dependency(span.file_id) {
return dep.log_with_span(level, span, message);
}
}
let (line, col) = self.position_from_offset(span.start.offset);
let line_content = self.source.lines().nth(line as usize - 1).unwrap_or("");
log::log!(
level,
"{}:{}:{}: {}\n {}\n {}{}",
self.path.display(),
line,
col,
message,
line_content,
" ".repeat(col as usize - 1),
"^"
);
}
}
8. Usage Example
Here's how you'd use this in practice:
```rust
// In your parser or code that needs to report errors
fn parse_token(&mut self, module: &CodeModule) -> Result<Token, AssemblerError> {
// ...
if !is_valid_token(&token) {
return Err(AssemblerError::new(
ErrorKind::SyntaxError,
token.span,
"Invalid token"
).with_context("Expected a valid instruction or directive"));
}
// ...
}
```
+6 -6
View File
@@ -2,10 +2,10 @@
// a simple brainf##k interpreter,
// because I already wrote a compiler lol.
include print "./lib/io/print.dsa"
include print "./lib/print.dsa"
// "print hello world"
db program: "++++++++++++++++++++++++++++++++++++++++++++
db program "++++++++++++++++++++++++++++++++++++++++++++
>++++++++++++++++++++++++++++++++
>++++++++++++++++
>
@@ -35,10 +35,10 @@ db program: "++++++++++++++++++++++++++++++++++++++++++++
]
<<++..."
db error: "Invalid Instruction!"
dw stack: 0x10000
dw input: 0x30000
resb data: 1024
db error "Invalid Instruction!"
dw stack 0x10000
dw input 0x30000
resb data 1024
// set up a stack so we can call functions
_init_stack:
Binary file not shown.
-121
View File
@@ -1,121 +0,0 @@
// GENERATED BY DSC COMPILER
// Generated at 2026-02-04 01:55:11
// Imports
include arena: "./lib/memory/arena_alloc.dsa"
include print: "./lib/io/print.dsa"
// Globals & Reserved Memory
// Entry Point
dw stack: 0x10000
db message: "Process Exited with code:"
_init:
ldw stack, bpr
mov bpr, spr
push zero
call main
call print::print_newline
lwi message, rg0
push rg0
call print::print
pop zero
call print::print_hex_word
pop zero
hlt
// Return
_ret:
mov bpr, spr
pop bpr
return
// Compiled Code Starts...
main:
push bpr
mov spr, bpr
lli 0, rg0
push rg0 // bpr-4: x
subi bpr 4 rg1
lli 512, rg0
push rg1 // bpr-8: y
push rg0 // push arg 0
call arena::new
pop rg2
lli 32, rg0
push rg2 // bpr-12: alloc
push rg0 // push arg 1
push rg2 // push arg 0
call arena::alloc
pop rg3
pop zero
lli 32, rg0
subi bpr 12 rg2
ldw rg2, rg2 // bpr-20: alloc
push rg2 // bpr-16: alloc
push rg3 // bpr-20: ptr1
push rg0 // push arg 1
push rg2 // push arg 0
call arena::alloc
pop rg4
pop zero
subi bpr 16 rg0
ldw rg0, rg0 // bpr-24: alloc
push rg0 // bpr-24: alloc
push rg4 // bpr-28: ptr2
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 20 rg0
ldw rg0, rg0 // bpr-28: ptr1
push rg0 // bpr-32: ptr1
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 28 rg0
ldw rg0, rg0 // bpr-36: ptr2
push rg0 // bpr-36: ptr2
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 36 rg0
ldw rg0, rg0 // bpr-44: ptr2
ldw rg0, rg2
push rg0 // bpr-40: ptr2
push rg2 // push arg 0
call print::print_num
pop zero
call print::print_newline
lli 42, rg2
subi bpr 40 rg5
ldw rg5, rg5 // bpr-48: ptr2
stw rg2, rg5
push rg5 // bpr-44: ptr2
push rg5 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 44 rg2
ldw rg2, rg2 // bpr-52: ptr2
ldw rg2, rg5
push rg2 // bpr-48: ptr2
push rg5 // push arg 0
call print::print_num
pop zero
call print::print_newline
db str_12: "end"
lwi str_12, rg5
push rg5 // push arg 0
call print::println
pop zero
lli 0, rg5
stw rg5, bpr, 8
jmp _ret
-32
View File
@@ -1,32 +0,0 @@
include print: "./lib/io/print.dsa";
include arena: "./lib/memory/arena_alloc.dsa";
fn main() -> u32 {
let x: u32 = 0;
let y: u32 = &x;
let alloc: u32 = arena::new(512);
let ptr1: u32 = arena::alloc(alloc, 32);
let ptr2: u32 = arena::alloc(alloc, 32);
print::print_hex_word(alloc);
print::print_newline();
print::print_hex_word(ptr1);
print::print_newline();
print::print_hex_word(ptr2);
print::print_newline();
print::print_num(*ptr2);
print::print_newline();
*ptr2 = 42;
print::print_hex_word(ptr2);
print::print_newline();
print::print_num(*ptr2);
print::print_newline();
print::println("end");
return 0;
}
-34
View File
@@ -1,34 +0,0 @@
include print "../io/print.dsa"
dw idt: 0xFFFF0000
setup_idt:
push bpr
mov spr, bpr
// load the IDT into the IDR
ldw idt, idr
mov bpr, spr
pop bpr
return
setup_hard_fault_handler:
push bpr
mov spr, bpr
lwi handle_hard_fault, rg0
stw rg0, idr, 4
mov bpr, spr
pop bpr
return
dw hard_fault_err: "FATAL: Illegal Instruction or Memory Access!"
handle_hard_fault:
call print::reset
lwi hard_fault_err, rg0
push rg0
call print::print
pop zero
hlt
+18
View File
@@ -0,0 +1,18 @@
fib_n:
pop ret
pop rg0 // n
lli 0, rg1
lli 1, rg2
start:
add rg1, rg2, acc
push rg1
mov rg2, rg1
mov acc, rg2
cmp rg0, zero
dec rg0
jgt start
jmp 4, ret
-331
View File
@@ -1,331 +0,0 @@
// lib:
// print.dsa
// usage:
//
// include print "<relative path>""
//
// usage for print:
// push (register containing address of string)
// push pcx
// jmp print::print
//
// usage for reset:
// push pcx
// jmp print::reset
//
// usage for clear:
// push pcx
// jmp print::clear
//
// usage for print_byte:
// push (register containing byte)
// push pcx
// jmp print::print_byte
//
// usage for print_word:
// push (register containing word)
// push pcx
// jmp print::print_word
//
// usage for print_num:
// push (register containing number to print in decimal)
// push pcx
// jmp print::print_num
//
include maths "../maths/core.dsa"
dw display: 0x20000
dw current: 0x20000
// ------------------------------------------
// prints the string at addr(arg[0]) to the screen. (no trailing whitespace unless explicitly provided)
print:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
_print_loop:
ldb rg0, acc
cmp acc, zero
jeq _end
stb acc, rg1
addi rg0, 1
addi rg1, 1
jmp _print_loop
// ------------------------------------------
println:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
_println_loop:
ldb rg0, acc
cmp acc, zero
jeq _println_end
stb acc, rg1
addi rg0, 1
addi rg1, 1
jmp _println_loop
_println_end:
call print_newline
jmp _end
// ------------------------------------------
// prints the value of arg[0] to the screen.
print_word:
// initialise
push bpr
mov spr, bpr
// load byte into acc
ldw bpr, rg0, 8
ldw current, rg1
addi rg1, 3
stb rg0, rg1
subi rg1, 1
shr rg0, 8
stb rg0, rg1
subi rg1, 1
shr rg0, 8
stb rg0, rg1
subi rg1, 1
shr rg0, 8
stb rg0, rg1
addi rg1, 4
jmp _end
// ------------------------------------------
// prints the last byte of arg[0] to the screen.
print_byte:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
stb rg0, rg1
addi rg1, 1
jmp _end
// ------------------------------------------
// prints the value of arg[0] to the screen in hex.
print_hex_word:
push bpr
mov spr, bpr
ldw current, rg1
ldb bpr, rg0, 8
push rg0
call _print_hex_byte
addi spr, 4
ldb bpr, rg0, 9
push rg0
call _print_hex_byte
addi spr, 4
ldb bpr, rg0, 10
push rg0
call _print_hex_byte
addi spr, 4
ldb bpr, rg0, 11
push rg0
call _print_hex_byte
addi spr, 4
jmp _end
// ------------------------------------------
// prints the last byte of arg[0] to the screen in hex.
print_hex_byte:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
call _print_hex_byte
jmp _end
// function body
_print_hex_byte:
// mask to get lower nibble
lli 0xF, rg2
// save rg0 state
push rg0
shr rg0, 4
and rg0, rg2, rg0
call _print_hex_nibble
pop rg0
and rg0, rg2, rg0
call _print_hex_nibble
return
// print a hex digit
_print_hex_nibble:
lli 10, rg3
cmp rg0, rg3
jlt _print_hex_nibble_number
addi rg0, 0x37, rg0
stb rg0, rg1
addi rg1, 1
return
// helper function.
_print_hex_nibble_number:
addi rg0, 0x30, rg0
stb rg0, rg1
addi rg1, 1
return
// ------------------------------------------
// print whitespace
print_whitespace:
push bpr
mov spr, bpr
ldw current, rg1
lli 0x20, rg0
stb rg0, rg1
addi rg1, 1
jmp _end
// ------------------------------------------
// print newline
print_newline:
push bpr
mov spr, bpr
// load variables into registers
ldw display, rg0
ldw current, rg1
// get the offset from the display base
sub rg1, rg0, rg0
lwi 80, rg2
pusha 3
push rg0
push rg2
call maths::divmod
pop zero // result
pop rg3 // remainder
popa 3
sub rg1, rg3, rg2
addi rg2, 80, rg1
// _end saves the display state
jmp _end
// ------------------------------------------
// prints arg[0] as a decimal number to the screen.
print_num:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // load number to print
lli 0, rg5 // rg5 = digit counter
// check if number is zero
cmp rg0, zero
jne _print_num_extract_digits
// special case: print '0' for zero
lli 0x30, rg6
push rg6 // push digit to stack buffer
lli 1, rg5 // we have 1 digit
jmp _print_num_output
_print_num_extract_digits:
// divide by 10 repeatedly to get digits
cmp rg0, zero
jeq _print_num_output
// call divmod(rg0, 10)
push rg0 // dividend
lli 10, rg1
push rg1 // divisor (10)
call maths::divmod
pop rg0 // quotient (continue dividing this)
pop rg1 // remainder (the digit)
// convert digit to ASCII and push to stack buffer
addi rg1, 0x30, rg6 // convert to ASCII
push rg6 // push digit to stack
inc rg5 // increment digit counter
jmp _print_num_extract_digits
_print_num_output:
// now print digits (pop them off in reverse order)
ldw current, rg1 // get display pointer
_print_num_output_loop:
// check if we've printed all digits
cmp rg5, zero
jeq _print_num_done
// pop digit and print it
pop rg6
stb rg6, rg1
addi rg1, 1
dec rg5
jmp _print_num_output_loop
_print_num_done:
jmp _end
// ------------------------------------------
// resets the cursor position on the screen to 0x20000. (0,0)
reset:
push bpr
mov spr, bpr
ldw display, rg1
jmp _end
// ------------------------------------------
// clears the screen
clear:
push bpr
mov spr, bpr
// display size = 2000 bytes / 500 words
lli 500 rg0
ldw display, rg1
_clear_loop:
dec rg0
stw zero, rg1
addi rg1, 4
cmp rg0, zero
jgt _clear_loop
jmp _end
// ------------------------------------------
// return
_end:
stw rg1, current
mov bpr, spr
pop bpr
return
-104
View File
@@ -1,104 +0,0 @@
// multiply.dsa
// usage:
//
// include multiply "<relative path>"
//
// usage for multiply:
// push (arg1)
// push (arg0)
// call multiply::multiply
// pop (arg0)
// pop (arg1)
multiply:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // load op 2
ldw bpr, rg1, 12 // load op 1
lwi 0, rg2 // initialise rg2 to zero
_multiply_loop:
add rg2, rg0, rg2
dec rg1
cmp rg1, zero
jgt _multiply_loop
_multiply_end:
stw rg2, bpr, 8
mov bpr, spr
pop bpr
return
divmod:
push bpr
mov spr, bpr
ldw bpr, rg1, 8 // load op 2
ldw bpr, rg0, 12 // load op 1
lli 0, rg3
_divmod_loop:
cmp rg0, rg1
jlt _divmod_end
sub rg0, rg1, rg0
inc rg3
jmp _divmod_loop
_divmod_end:
// store div in first arg
// store mod in second arg
stw rg3, bpr, 8
stw rg0, bpr, 12
mov bpr, spr
pop bpr
return
// multiply.dsa - improved version
// Multiplies two 32-bit numbers using shift-and-add
//
// Usage:
// push operand2 (multiplier)
// push operand1 (multiplicand)
// call multiply::multiply
// pop result
// pop zero (discard second argument)
new_multiply:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // rg0 = multiplicand
ldw bpr, rg1, 12 // rg1 = multiplier
lli 0, rg2 // rg2 = result (accumulator)
lli 32, rg3 // rg3 = bit counter
mult_loop:
// Check if lowest bit of multiplier is 1
lli 1, acc
and rg1, acc, acc // acc = rg1 & 1
cmp acc, zero
jeq skip_add // if (rg1 & 1) == 0, skip addition
// Add multiplicand to result
add rg2, rg0, rg2
skip_add:
shl rg0, 1 // shift multiplicand left
shr rg1, 1 // shift multiplier right
dec rg3
cmp rg3, zero
jgt mult_loop
stw rg2, bpr, 8 // store result
mov bpr, spr
pop bpr
return
-24
View File
@@ -1,24 +0,0 @@
include print "../io/print.dsa"
fib_n:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // load arg
lwi 0, rg1
lwi 1, rg2
_start:
add rg1, rg2, rg3
mov rg2, rg1
mov rg3, rg2
dec rg0
cmp rg0, zero
jgt _start
stw rg3, bpr, 8
mov bpr, spr
pop bpr
return
-100
View File
@@ -1,100 +0,0 @@
dw heap_start: 196608
dw heap_end: 262144
dw heap_current: 196608
new:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
lli 12, rg1
add rg0, rg1, rg2
ldw heap_current, rg1
add rg1, rg2, rg3
ldw heap_end, rg4
cmp rg3, rg4
lli 0, rg5
jle _cmp_end_2
lli 1, rg5
_cmp_end_2:
cmp rg5, zero
jeq _else_4
_then_3:
lli 0, rg4
stw rg4, bpr, 8
jmp _ret
jmp _end_5
_else_4:
nop
_end_5:
lli 12, rg4
add rg1, rg4, rg5
add rg1, rg2, rg4
stw rg5, rg1
lli 4, rg6
add rg1, rg6, rg7
stw rg5, rg7
lli 8, rg6
add rg1, rg6, rg7
stw rg4, rg7
stw rg3, heap_current
stw rg1, bpr, 8
jmp _ret
alloc:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw bpr, rg1, 12
lli 4, rg2
add rg0, rg2, rg3
ldw rg3, rg2
lli 8, rg3
add rg0, rg3, rg4
ldw rg4, rg3
add rg2, rg1, rg4
cmp rg4, rg3
lli 0, rg5
jle _cmp_end_6
lli 1, rg5
_cmp_end_6:
cmp rg5, zero
jeq _else_8
_then_7:
lli 0, rg5
stw rg5, bpr, 8
jmp _ret
jmp _end_9
_else_8:
nop
_end_9:
lli 4, rg5
add rg0, rg5, rg6
stw rg4, rg6
stw rg2, bpr, 8
jmp _ret
destroy:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
lli 0, rg1
stw rg1, bpr, 8
jmp _ret
reset_all:
push bpr
mov spr, bpr
ldw heap_start, rg0
stw rg0, heap_current
lli 0, rg0
stw rg0, bpr, 8
jmp _ret
_ret:
mov bpr, spr
pop bpr
return
-77
View File
@@ -1,77 +0,0 @@
// Arena Allocator
// Supports multiple arenas that can be destroyed independently
// Much more practical than a simple bump allocator
// Global heap management
static heap_start: u32 = 0x30000;
static heap_end: u32 = 0x40000;
static heap_current: u32 = 0x30000;
// Arena structure (stored at the start of each arena):
// [0-3]: start_address (u32)
// [4-7]: current_position (u32)
// [8-11]: end_address (u32)
// Total header size: 12 bytes
// Create a new arena with given size
// Returns pointer to arena handle (or 0 if failed)
fn arena_create(size: u32) -> u32 {
let total_size: u32 = size + 12;
let arena_ptr: u32 = heap_current;
let new_current: u32 = arena_ptr + total_size;
// Check if we have space
if new_current > heap_end {
return 0;
}
// Calculate arena data region
let data_start: u32 = arena_ptr + 12;
let data_end: u32 = arena_ptr + total_size;
// Initialize arena header
// Note: In real implementation, you'd use pointer writes here
// For now, using placeholder comments:
*arena_ptr = data_start; // start_address
*(arena_ptr + 4) = data_start; // current_position
*(arena_ptr + 8) = data_end; // end_address
heap_current = new_current;
return arena_ptr;
}
// Allocate from an arena
// Returns pointer to allocated memory (or 0 if failed)
fn arena_alloc(arena: u32, size: u32) -> u32 {
// Read current position from arena
let current: u32 = *(arena + 4);
let end: u32 = *(arena + 8);
let new_current: u32 = current + size;
// Check if arena has space
if new_current > end {
return 0;
}
// Update current position in arena
*(arena + 4) = new_current;
return current;
}
// Destroy an arena (in bump allocator, this is a no-op)
// In a real allocator, you'd mark the memory as free
fn arena_destroy(arena: u32) {
// In a true allocator, mark memory as reusable
// For bump allocator, we can't reclaim memory
// unless we destroy ALL arenas and reset
return 0;
}
// Reset entire heap (destroys ALL arenas)
fn reset_all() {
heap_current = heap_start;
return 0;
}
+30
View File
@@ -0,0 +1,30 @@
// multiply.dsa
// usage:
//
// include multiply "<relative path>"
//
// usage for multiply:
// push (arg1)
// push (arg0)
// call multiply::multiply
// pop (arg0)
// pop (arg1)
multiply:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // load op 1
ldw bpr, rg1, 12 // load op 2
start:
add acc, rg0, acc
dec rg1
cmp rg1, zero
jgt start
end:
mov bpr, spr
pop bpr
return
+115
View File
@@ -0,0 +1,115 @@
// lib:
// print.dsa
// usage:
//
// include print "<relative path>""
//
// usage for print:
// push (register containing address of string)
// push pcx
// jmp print::print
//
// usage for reset:
// push pcx
// jmp print::reset
//
// usage for clear:
// push pcx
// jmp print::clear
//
// usage for print_byte:
// push (register containing byte)
// push pcx
// jmp print::print_byte
//
// usage for print_word:
// push (register containing word)
// push pcx
// jmp print::print_word
//
dw display: 0x20000
dw current: 0x20000
// ------------------------------------------
// prints the string at addr(arg[0]) to the screen.
print:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
_print_loop:
ldb rg0, acc
stb acc, rg1
addi rg0, 1
addi rg1, 1
cmp acc, zero
jne _print_loop
jmp _end
// ------------------------------------------
// prints the value of arg[0] to the screen.
print_word:
// initialise
push bpr
mov spr, bpr
// load byte into acc
ldw bpr, rg0, 8
ldw current, rg1
stw rg0, rg1
addi rg1, 4
jmp _end
// ------------------------------------------
// prints the last byte of arg[0] to the screen.
print_byte:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
stb rg0, rg1
addi rg1, 1
jmp _end
// ------------------------------------------
// resets the cursor position on the screen to 0x20000. (0,0)
reset:
push bpr
mov spr, bpr
ldw display, rg1
jmp _end
// ------------------------------------------
// clears the screen
clear:
push bpr
mov spr, bpr
// display size = 2000 bytes / 500 words
lli 500 rg0
ldw display, rg1
_clear_loop:
dec rg0
stw zero, rg1
addi rg1, 4
cmp rg0, zero
jgt _clear_loop
jmp _end
// ------------------------------------------
// return
_end:
stw rg1, current
mov bpr, spr
pop bpr
return
-51
View File
@@ -1,51 +0,0 @@
// GENERATED BY DSC COMPILER
// Generated at 2026-02-04 01:44:06
// Imports
include print: "./lib/io/print.dsa"
include fib: "./lib/maths/fib.dsa"
// Globals & Reserved Memory
// Entry Point
dw stack: 0x10000
db message: "Process Exited with code:"
_init:
ldw stack, bpr
mov bpr, spr
push zero
call main
call print::print_newline
lwi message, rg0
push rg0
call print::print
pop zero
call print::print_hex_word
pop zero
hlt
// Return
_ret:
mov bpr, spr
pop bpr
return
// Compiled Code Starts...
main:
push bpr
mov spr, bpr
lli 6, rg0
push rg0 // bpr-4: x
push rg0 // push arg 0
call fib::fib_n
pop rg1
push rg1 // bpr-8: y
push rg1 // push arg 0
call print::print_num
pop zero
jmp _ret
-9
View File
@@ -1,9 +0,0 @@
include print: "./lib/io/print.dsa";
include fib: "./lib/maths/fib.dsa";
fn main() -> u32 {
let x: u32 = 6;
let y: u32 = fib::fib_n(x);
print::print_num(y);
}
-214
View File
@@ -1,214 +0,0 @@
// GENERATED BY DSC COMPILER
// Generated at 2026-02-03 23:37:16
// Imports
include print: "./lib/io/print.dsa"
// Globals & Reserved Memory
dw heap_start: 196608
dw heap_end: 262144
dw heap_current: 196608
// Entry Point
dw stack: 0x10000
db message: "Process Exited with code:"
_init:
ldw stack, bpr
mov bpr, spr
push zero
call main
call print::print_newline
lwi message, rg0
push rg0
call print::print
pop zero
call print::print_hex_word
pop zero
hlt
// Return
_ret:
mov bpr, spr
pop bpr
return
// Compiled Code Starts...
main:
push bpr
mov spr, bpr
lli 0, rg0
push rg0 // bpr-4: x
subi bpr 4 rg1
lli 512, rg0
push rg1 // bpr-8: y
push rg0 // push arg 0
call arena_create
pop rg2
lli 32, rg0
push rg2 // bpr-12: alloc
push rg0 // push arg 1
push rg2 // push arg 0
call arena_alloc
pop rg3
pop zero
lli 32, rg0
subi bpr 12 rg2
ldw rg2, rg2 // bpr-20: alloc
push rg3 // bpr-16: ptr1
push rg2 // bpr-20: alloc
push rg0 // push arg 1
push rg2 // push arg 0
call arena_alloc
pop rg4
pop zero
subi bpr 20 rg0
ldw rg0, rg0 // bpr-28: alloc
push rg4 // bpr-24: ptr2
push rg0 // bpr-28: alloc
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 16 rg0
ldw rg0, rg0 // bpr-24: ptr1
push rg0 // bpr-32: ptr1
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 24 rg0
ldw rg0, rg0 // bpr-32: ptr2
push rg0 // bpr-36: ptr2
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 36 rg0
ldw rg0, rg0 // bpr-44: ptr2
ldw rg0, rg2
push rg0 // bpr-40: ptr2
push rg2 // push arg 0
call print::print_num
pop zero
call print::print_newline
lli 42, rg2
subi bpr 40 rg5
ldw rg5, rg5 // bpr-48: ptr2
stw rg2, rg5
push rg5 // bpr-44: ptr2
push rg5 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 44 rg2
ldw rg2, rg2 // bpr-52: ptr2
ldw rg2, rg5
push rg2 // bpr-48: ptr2
push rg5 // push arg 0
call print::print_num
pop zero
call print::print_newline
db str_1: "end"
lwi str_1, rg5
push rg5 // push arg 0
call print::println
pop zero
lli 0, rg5
stw rg5, bpr, 8
jmp _ret
arena_create:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
lli 12, rg1
add rg0, rg1, rg2
ldw heap_current, rg1
add rg1, rg2, rg3
ldw heap_end, rg4
cmp rg3, rg4
lli 0, rg5
jle _cmp_end_2
lli 1, rg5
_cmp_end_2:
cmp rg5, zero
jeq _else_4
_then_3:
lli 0, rg4
stw rg4, bpr, 8
jmp _ret
jmp _end_5
_else_4:
nop
_end_5:
lli 12, rg4
add rg1, rg4, rg5
add rg1, rg2, rg4
stw rg5, rg1
lli 4, rg6
add rg1, rg6, rg7
stw rg5, rg7
lli 8, rg6
add rg1, rg6, rg7
stw rg4, rg7
stw rg3, heap_current
stw rg1, bpr, 8
jmp _ret
arena_alloc:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw bpr, rg1, 12
lli 4, rg2
add rg0, rg2, rg3
ldw rg3, rg2
lli 8, rg3
add rg0, rg3, rg4
ldw rg4, rg3
add rg2, rg1, rg4
cmp rg4, rg3
lli 0, rg5
jle _cmp_end_6
lli 1, rg5
_cmp_end_6:
cmp rg5, zero
jeq _else_8
_then_7:
lli 0, rg5
stw rg5, bpr, 8
jmp _ret
jmp _end_9
_else_8:
nop
_end_9:
lli 4, rg5
add rg0, rg5, rg6
stw rg4, rg6
stw rg2, bpr, 8
jmp _ret
arena_destroy:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
lli 0, rg1
stw rg1, bpr, 8
jmp _ret
reset_all:
push bpr
mov spr, bpr
ldw heap_start, rg0
stw rg0, heap_current
lli 0, rg0
stw rg0, bpr, 8
jmp _ret
+18
View File
@@ -0,0 +1,18 @@
include print "./lib/print.dsa"
dw stack: 0x10000
db string: "Hello world"
init:
// set up a stack.
ldw stack, bpr
mov bpr, spr
start:
lwi string, rg1
push rg1
call print::print
pop rg1
hlt
-80
View File
@@ -1,80 +0,0 @@
include print "./lib/io/print.dsa"
dw idt: 0xFFFF0000
dw stack: 0x10000
init:
// setup interrupt handlers
ldw idt, idr
lwi handle_hard_fault, rg0
stw rg0, idr, 4
// set up a stack.
ldw stack, bpr
mov bpr, spr
db string: "I won, the game!"
db hexbyte: 0xab
dw hexword: 0x1234abcd
db replace: "I lost"
start:
// test print string
lwi string, rg0
push rg0
call print::print
pop zero
// test print hex byte.
ldb hexbyte, rg0
push rg0
call print::print_hex_byte
pop zero
// test print hex word.
ldw hexword, rg0
push rg0
call print::print_hex_word
pop zero
// test print char
lli 0x40, rg0 // print @
push rg0
call print::print_byte
pop zero
// test newline
call print::print_newline
lwi string rg0
push rg0
call print::print
// test print word
lwi 0x31323334, rg0 // print 1234
push rg0
call print::print_word
pop zero
// test reset cursor pos
call print::reset
// test print string at reset pos
lwi replace, rg0
push rg0
call print::print
pop zero
hlt
// fault handler in case we fail DSA.
dw hard_fault_err: "FATAL: Illegal Instruction or Memory Access!"
handle_hard_fault:
call print::clear
call print::reset
lwi hard_fault_err, rg0
push rg0
call print::print
pop zero
hlt

Some files were not shown because too many files have changed in this diff Show More