16 Commits

Author SHA1 Message Date
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
120 changed files with 10111 additions and 16524 deletions
-4
View File
@@ -5,7 +5,3 @@ rustc-wrapper = "sccache"
[future-incompat-report] [future-incompat-report]
frequency = "always" frequency = "always"
[profile.profiling]
inherits = "release"
debug = true
+1 -2
View File
@@ -1,3 +1,2 @@
/target /target
**/*.env **/*.env
Cargo.lock
+1 -7
View File
@@ -5,11 +5,5 @@
"files.eol": "\n", "files.eol": "\n",
"files.insertFinalNewline": true, "files.insertFinalNewline": true,
"files.trimFinalNewlines": true, "files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true, "files.trimTrailingWhitespace": true
"gitea.owner": "LowLevelDevs",
"gitea.repo": "damn_simple_architecture",
"[markdown]": {
"editor.formatOnSave": true,
"editor.formatOnPaste": true
}
} }
-15
View File
@@ -1,15 +0,0 @@
// Folder-specific settings
//
// For a full list of overridable settings, and general information on folder-specific settings,
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
{
"lsp": {
"rust-analyzer": {
"initialization_options": {
"check": {
"command": "clippy", // rust-analyzer.check.command (default: "check")
},
},
},
},
}
-37
View File
@@ -1,37 +0,0 @@
[
{
"label": "Run Emulator",
"command": "cargo run --bin emulator",
"use_new_terminal": true,
},
{
"label": "Run Compiler",
"command": "cargo run --bin compiler",
"use_new_terminal": true,
},
{
"label": "Run Assembler",
"command": "cargo run --bin assembler",
"use_new_terminal": true,
},
{
"label": "Run Build System (dsx-build)",
"command": "cargo run --bin dsx-build",
"use_new_terminal": true,
},
{
"label": "Build All (Release)",
"command": "cargo build --release",
"use_new_terminal": false,
},
{
"label": "Run Tests",
"command": "cargo test",
"use_new_terminal": true,
},
{
"label": "Profile Emulator with perf",
"command": "cargo build --profile profiling; perf record -g -F 999 target/profiling/emulator; perf script -F +pid | save test.perf",
"use_new_terminal": true,
},
]
Generated
+4406
View File
File diff suppressed because it is too large Load Diff
+3 -7
View File
@@ -1,7 +1,7 @@
cargo-features = ["codegen-backend"] cargo-features = ["codegen-backend"]
[workspace] [workspace]
members = ["emulator", "common", "assembler", "dsa_editor", "compiler", "dsx-build"] members = ["emulator", "common", "assembler", "dsa_editor"]
resolver = "3" resolver = "3"
[workspace.package] [workspace.package]
@@ -11,11 +11,7 @@ authors = ["zxq5", "nullndvoid"]
[profile.dev] [profile.dev]
codegen-backend = "cranelift" codegen-backend = "cranelift"
panic = "abort" # Cranelift does not support stack unwinds. panic = "abort" # Cranelift does not support stack unwinds.
lto = false lto = false
debug = true debug = true
incremental = false # sccache does not support caching incremental crates. incremental = false # sccache does not support caching incremental crates.
[profile.release]
debug = true
lto = "fat"
+3 -1
View File
@@ -5,7 +5,7 @@ edition.workspace = true
authors.workspace = true authors.workspace = true
[[bin]] [[bin]]
name = "assembler" name = "assembler_runner"
path = "src/main.rs" path = "src/main.rs"
[lib] [lib]
@@ -13,6 +13,8 @@ name = "assembler"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
clap = { version = "4.5.40", features = ["derive"] }
common = { path = "../common" } common = { path = "../common" }
num_cpus = "1.17.0" num_cpus = "1.17.0"
threadpool = "1.8.1" threadpool = "1.8.1"
uuid = { version = "1.17.0", features = ["v4"] }
+20
View File
@@ -0,0 +1,20 @@
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>,
}
-359
View File
@@ -1,359 +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(src_reg) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(r_shamt) = args.get(1) else {
return Err(AssembleError::MissingArgument(0));
};
let Some(i_shamt) = args.get(2) else {
return Err(AssembleError::MissingArgument(1));
};
let Some(dest_reg) = args.get(3) else {
return Err(AssembleError::MissingArgument(1));
};
let src = expect_token!(src_reg, Register)?;
let r_shamt = expect_token!(r_shamt, Register)?;
let i_shamt = expect_token!(i_shamt, Immediate)? as u8;
let dest = expect_token!(dest_reg, Register)?;
match opcode {
Opcode::Shl => Ok(Instruction::ShiftLeft(
args!(R, sr1: src, sr2: r_shamt, shamt: i_shamt, dr: dest),
)),
Opcode::Shr => Ok(Instruction::ShiftRight(
args!(R, sr1: src, sr2: r_shamt, shamt: i_shamt, dr: dest),
)),
_ => unreachable!(),
}
}
fn build_arithmetic_instruction(
opcode: Opcode,
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(left_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(right_token) = args.get(1) else {
return Err(AssembleError::MissingArgument(1));
};
let Some(dest_token) = args.get(2) else {
return Err(AssembleError::MissingArgument(2));
};
let left = expect_token!(left_token, Register)?;
let right = expect_token!(right_token, Register)?;
let dest = expect_token!(dest_token, Register)?;
let instruction_args = args!(R, sr1: left, sr2: right, dr: dest);
match opcode {
Opcode::Add => Ok(Instruction::Add(instruction_args)),
Opcode::Sub => Ok(Instruction::Sub(instruction_args)),
Opcode::And => Ok(Instruction::And(instruction_args)),
Opcode::Or => Ok(Instruction::Or(instruction_args)),
Opcode::Xor => Ok(Instruction::Xor(instruction_args)),
Opcode::Nand => Ok(Instruction::Nand(instruction_args)),
Opcode::Nor => Ok(Instruction::Nor(instruction_args)),
Opcode::Xnor => Ok(Instruction::Xnor(instruction_args)),
_ => unreachable!(),
}
}
fn build_arithmetic_immediate_instruction(
opcode: Opcode,
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(reg_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(immediate_token) = args.get(1) else {
return Err(AssembleError::MissingArgument(1));
};
let Some(dest_token) = args.get(2) else {
return Err(AssembleError::MissingArgument(2));
};
let reg = expect_token!(reg_token, Register)?;
let immediate = expect_token!(immediate_token, Immediate)? as u16;
let dest = expect_token!(dest_token, Register)?;
let instruction_args = args!(I, immediate: immediate, r1: reg, r2: dest);
match opcode {
Opcode::AddI => Ok(Instruction::AddImmediate(instruction_args)),
Opcode::SubI => Ok(Instruction::SubImmediate(instruction_args)),
_ => unreachable!(),
}
}
fn build_not_instruction(
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(reg_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(dest_token) = args.get(1) else {
return Err(AssembleError::MissingArgument(1));
};
let reg = expect_token!(reg_token, Register)?;
let dest = expect_token!(dest_token, Register)?;
Ok(Instruction::Not(args!(R, sr1: reg, dr: dest)))
}
fn build_interrupt_instruction(
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(code_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let code = expect_token!(code_token, Immediate)? as u8;
Ok(Instruction::Interrupt(Interrupt::Software(code)))
}
fn build_data_instruction(
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(immediate_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let immediate = expect_token!(immediate_token, Immediate)?;
Ok(Instruction::Data(immediate))
}
fn build_segment_instruction(
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(immediate_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let immediate = expect_token!(immediate_token, Immediate)?;
Ok(Instruction::Segment(immediate))
}
-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),
})))
}
}
-139
View File
@@ -1,139 +0,0 @@
//! Macros used throughout the assembler
use crate::assembler::model::{Node, Opcode, Symbol, Token};
/// Parse DSA assembly code with optional formatting
///
/// # Examples
/// ```rs
/// use assembler::macros::dsa;
/// // With formatting:
/// let nodes = dsa!(hash, "mov r1, {}", 42)?;
///
/// // Without formatting:
/// let nodes = dsa!(hash, "mov r1, 42")?;
/// ```
#[macro_export]
macro_rules! dsa {
// Version with formatting arguments
($hash:expr, $input:expr, $($args:expr),+) => {{
let input = format!($input, $($args),+);
let tokens = $crate::lexer::lexer(input, $hash)?;
let parsed = $crate::parser::Parser::parse_nodes(tokens)?;
parsed
}};
// Version without formatting
($hash:expr, $input:expr) => {{
let input = String::from($input);
let tokens = $crate::lexer::lexer(input, $hash)?;
let parsed = $crate::parser::Parser::parse_nodes(tokens)?;
parsed
}};
}
/// Creates a new Node with the given symbol, opcode, and tokens
#[macro_export]
macro_rules! node {
($symbol: expr, $opcode: expr, args: $tokens: expr) => {
$crate::assembler::model::Node::new($symbol.clone(), $opcode.clone(), $tokens.clone())
};
($symbol: expr, $opcode: expr, $($tokens: expr),+) => {
$crate::assembler::model::Node::new(
$symbol.clone(),
$opcode.clone(),
vec![$(node!(@convert_token $tokens)),+]
)
};
($symbol: expr, $opcode: expr) => {
$crate::assembler::model::Node::new(
$symbol.clone(),
$opcode.clone(),
Vec::new()
)
};
(@convert_token $token: literal) => {
$crate::assembler::model::Token::Immediate($token)
};
(@convert_token $token: expr) => {
$token.clone()
};
}
/// Extracts a specific token type from a token
#[macro_export]
macro_rules! expect_token {
($token:expr, Symbol) => {
match $token {
$crate::assembler::model::Token::Symbol(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::Symbol,
)),
}
};
($token:expr, Register) => {
match $token {
$crate::assembler::model::Token::Register(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::Register,
)),
}
};
($token:expr, Immediate) => {
match $token {
$crate::assembler::model::Token::Immediate(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::Immediate,
)),
}
};
($token:expr, StringLit) => {
match $token {
$crate::assembler::model::Token::StringLit(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::StringLit,
)),
}
};
($token:expr, Opcode) => {
match $token {
$crate::assembler::model::Token::Opcode(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::Opcode,
)),
}
};
}
/// Checks if a token matches any of the specified types
#[macro_export]
macro_rules! expect_type {
($token:expr, $($variant:ident),+) => {{
let token = $token;
match &token {
$(
$crate::assembler::model::Token::$variant(_) => Ok(token.clone()),
)+
other => {
let expected_type = expect_type!(@get_first_type $($variant),+);
Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone().clone(),
expected_type,
))
}
}
}};
(@get_first_type Symbol $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Symbol };
(@get_first_type Register $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Register };
(@get_first_type Immediate $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Immediate };
(@get_first_type StringLit $(, $rest:ident)*) => { $crate::assembler::model::TokenType::StringLit };
(@get_first_type Opcode $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Opcode };
}
-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()
}
-438
View File
@@ -1,438 +0,0 @@
use std::{fmt, str::FromStr};
use common::prelude::Register;
use crate::assembler::AssembleError;
#[derive(Debug, Clone)]
pub struct Node {
pub symbol: Option<Symbol>,
pub opcode: Opcode,
pub tokens: Vec<Token>,
}
impl Node {
#[must_use]
pub const fn new(symbol: Option<Symbol>, opcode: Opcode, tokens: Vec<Token>) -> Self {
Self {
symbol,
opcode,
tokens,
}
}
#[must_use]
pub fn label(&self) -> Option<Symbol> {
self.symbol.clone()
}
#[must_use]
pub const fn opcode(&self) -> Opcode {
self.opcode
}
#[must_use]
pub fn args(&self) -> Vec<Token> {
self.tokens.clone()
}
pub fn arg(&self, index: usize) -> Result<Token, AssembleError> {
self.args()
.get(index)
.cloned()
.ok_or(AssembleError::InvalidArg)
}
}
impl fmt::Display for Node {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let symbol = self
.label()
.as_ref()
.map_or_else(String::new, |symbol| format!("{symbol}:\n"));
let args = self
.args()
.into_iter()
.map(|arg| arg.to_string())
.collect::<Vec<_>>()
.join(" ");
write!(
f,
"\x1b[93m{} \t\x1b[94m{} \x1b[37m{} \x1b[0m",
symbol,
self.opcode(),
args,
)
}
}
impl fmt::Display for Symbol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} [ID:{}]", self.name, self.module)
}
}
impl fmt::Display for Module {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Unresolved(name) => write!(f, "{name}"),
Self::Resolved(name) => write!(f, "{name}"),
}
}
}
impl fmt::Display for Opcode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Nop => write!(f, "nop"),
Self::Mov => write!(f, "mov"),
Self::Movs => write!(f, "movs"),
Self::Ldb => write!(f, "ldb"),
Self::Ldbs => write!(f, "ldbs"),
Self::Ldh => write!(f, "ldh"),
Self::Ldhs => write!(f, "ldhs"),
Self::Ldw => write!(f, "ldw"),
Self::Stb => write!(f, "stb"),
Self::Sth => write!(f, "sth"),
Self::Stw => write!(f, "stw"),
Self::Lli => write!(f, "lli"),
Self::Lui => write!(f, "lui"),
Self::Jmp => write!(f, "jmp"),
Self::Jeq => write!(f, "jeq"),
Self::Jne => write!(f, "jne"),
Self::Jgt => write!(f, "jgt"),
Self::Jge => write!(f, "jge"),
Self::Jlt => write!(f, "jlt"),
Self::Jle => write!(f, "jle"),
Self::Cmp => write!(f, "cmp"),
Self::Inc => write!(f, "inc"),
Self::Dec => write!(f, "dec"),
Self::Shl => write!(f, "shl"),
Self::Shr => write!(f, "shr"),
Self::Add => write!(f, "add"),
Self::Sub => write!(f, "sub"),
Self::And => write!(f, "and"),
Self::Or => write!(f, "or"),
Self::Not => write!(f, "not"),
Self::Xor => write!(f, "xor"),
Self::Nand => write!(f, "nand"),
Self::Nor => write!(f, "nor"),
Self::Xnor => write!(f, "xnor"),
Self::Int => write!(f, "int"),
Self::Irt => write!(f, "irt"),
Self::Hlt => write!(f, "hlt"),
Self::AddI => write!(f, "addi"),
Self::SubI => write!(f, "subi"),
Self::Db => write!(f, "db"),
Self::Dh => write!(f, "dh"),
Self::Dw => write!(f, "dw"),
Self::Resb => write!(f, "resb"),
Self::Resh => write!(f, "resh"),
Self::Resw => write!(f, "resw"),
Self::Push => write!(f, "push"),
Self::Pop => write!(f, "pop"),
Self::Lwi => write!(f, "lwi"),
Self::Call => write!(f, "call"),
Self::Return => write!(f, "return"),
Self::Pusha => write!(f, "pusha"),
Self::Popa => write!(f, "popa"),
// meta instructions
Self::Include => write!(f, "include"),
Self::Data => write!(f, "data"),
Self::Segment => write!(f, "[SEGMENT]"),
}
}
}
#[derive(Debug, Clone, Eq)]
pub struct Symbol {
pub name: String,
pub module: Module,
}
impl std::hash::Hash for Symbol {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.module.hash(state);
}
}
impl PartialEq for Symbol {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.module == other.module
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Module {
Resolved(u64),
Unresolved(String),
}
#[derive(Debug, Clone)]
pub enum Token {
Symbol(Symbol),
Register(Register),
Immediate(u32),
StringLit(String),
Opcode(Opcode),
}
impl fmt::Display for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Symbol(symbol) => write!(f, "{symbol}"),
Self::Register(register) => write!(f, "{register}",),
Self::Immediate(immediate) => write!(f, "{immediate}",),
Self::StringLit(string_lit) => write!(f, "{string_lit}",),
Self::Opcode(opcode) => write!(f, "{opcode}",),
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum TokenType {
Symbol,
Register,
Immediate,
StringLit,
Opcode,
}
impl TokenType {
#[must_use]
pub const fn from_token(token: &Token) -> Self {
match token {
Token::Symbol(_) => Self::Symbol,
Token::Register(_) => Self::Register,
Token::Immediate(_) => Self::Immediate,
Token::StringLit(_) => Self::StringLit,
Token::Opcode(_) => Self::Opcode,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Opcode {
// Real instructions (0x00-0x26)
Nop,
Mov,
Movs,
Ldb,
Ldbs,
Ldh,
Ldhs,
Ldw,
Stb,
Sth,
Stw,
Lli,
Lui,
Jmp,
Jeq,
Jne,
Jgt,
Jge,
Jlt,
Jle,
Cmp,
Inc,
Dec,
Shl,
Shr,
Add,
Sub,
And,
Or,
Not,
Xor,
Nand,
Nor,
Xnor,
Int,
Irt,
Hlt,
AddI,
SubI,
// Pseudo-instructions
Db,
Dh,
Dw,
Resb,
Resh,
Resw,
Push,
Pop,
Pusha,
Popa,
Lwi,
Call,
Return,
// meta instructions (these aren't present in the binary as instructions)
Include,
Data,
Segment,
}
#[derive(Debug)]
pub enum OpcodeFromStrError {
InvalidRegister(&'static str),
InvalidOpcode(String),
}
impl std::fmt::Display for OpcodeFromStrError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidRegister(reg) => write!(f, "register does not exist: {reg}"),
Self::InvalidOpcode(op) => write!(f, "instruction does not exist: {op}"),
}
}
}
impl std::error::Error for OpcodeFromStrError {}
impl FromStr for Opcode {
type Err = OpcodeFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"nop" => Ok(Self::Nop),
"mov" => Ok(Self::Mov),
"movs" => Ok(Self::Movs),
"ldb" => Ok(Self::Ldb),
"ldbs" => Ok(Self::Ldbs),
"ldh" => Ok(Self::Ldh),
"ldhs" => Ok(Self::Ldhs),
"ldw" => Ok(Self::Ldw),
"stb" => Ok(Self::Stb),
"sth" => Ok(Self::Sth),
"stw" => Ok(Self::Stw),
"lli" => Ok(Self::Lli),
"lui" => Ok(Self::Lui),
"jmp" => Ok(Self::Jmp),
"jeq" => Ok(Self::Jeq),
"jne" => Ok(Self::Jne),
"jgt" => Ok(Self::Jgt),
"jge" => Ok(Self::Jge),
"jlt" => Ok(Self::Jlt),
"jle" => Ok(Self::Jle),
"cmp" => Ok(Self::Cmp),
"inc" => Ok(Self::Inc),
"dec" => Ok(Self::Dec),
"shl" => Ok(Self::Shl),
"shr" => Ok(Self::Shr),
"add" => Ok(Self::Add),
"sub" => Ok(Self::Sub),
"and" => Ok(Self::And),
"or" => Ok(Self::Or),
"not" => Ok(Self::Not),
"xor" => Ok(Self::Xor),
"nand" => Ok(Self::Nand),
"nor" => Ok(Self::Nor),
"xnor" => Ok(Self::Xnor),
"int" => Ok(Self::Int),
"irt" => Ok(Self::Irt),
"hlt" => Ok(Self::Hlt),
"addi" => Ok(Self::AddI),
"subi" => Ok(Self::SubI),
"db" => Ok(Self::Db),
"dh" => Ok(Self::Dh),
"dw" => Ok(Self::Dw),
"resb" => Ok(Self::Resb),
"resh" => Ok(Self::Resh),
"resw" => Ok(Self::Resw),
"push" => Ok(Self::Push),
"pop" => Ok(Self::Pop),
"lwi" => Ok(Self::Lwi),
"include" => Ok(Self::Include),
"call" => Ok(Self::Call),
"return" => Ok(Self::Return),
"pusha" => Ok(Self::Pusha),
"popa" => Ok(Self::Popa),
_ => Err(OpcodeFromStrError::InvalidOpcode(s.to_string())),
}
}
}
impl Opcode {
pub const OPCODES: &[&str] = &[
// Real instructions (0x00-0x26)
"nop", "mov", "movs", "ldb", "ldbs", "ldh", "ldhs", "ldw", "stb", "sth", "stw",
"lli", "lui", "jmp", "jeq", "jne", "jgt", "jge", "jlt", "jle", "cmp", "inc",
"dec", "shl", "shr", "add", "sub", "and", "or", "not", "xor", "nand", "nor",
"xnor", "int", "irt", "hlt", "addi", "subi", // Pseudo-instructions
"db", "dh", "dw", "resb", "resh", "resw", "push", "pop", "lwi", "call", "return",
"pusha", "popa", // meta instructions
"include",
];
#[must_use]
pub const fn to_opcode_value(&self) -> Option<u8> {
match self {
Self::Nop => Some(0x00),
Self::Mov => Some(0x01),
Self::Movs => Some(0x02),
Self::Ldb => Some(0x03),
Self::Ldbs => Some(0x04),
Self::Ldh => Some(0x05),
Self::Ldhs => Some(0x06),
Self::Ldw => Some(0x07),
Self::Stb => Some(0x08),
Self::Sth => Some(0x09),
Self::Stw => Some(0x0A),
Self::Lli => Some(0x0B),
Self::Lui => Some(0x0C),
Self::Jmp => Some(0x0D),
Self::Jeq => Some(0x0E),
Self::Jne => Some(0x0F),
Self::Jgt => Some(0x10),
Self::Jge => Some(0x11),
Self::Jlt => Some(0x12),
Self::Jle => Some(0x13),
Self::Cmp => Some(0x14),
Self::Inc => Some(0x15),
Self::Dec => Some(0x16),
Self::Shl => Some(0x17),
Self::Shr => Some(0x18),
Self::Add => Some(0x19),
Self::Sub => Some(0x1A),
Self::And => Some(0x1B),
Self::Or => Some(0x1C),
Self::Not => Some(0x1D),
Self::Xor => Some(0x1E),
Self::Nand => Some(0x1F),
Self::Nor => Some(0x20),
Self::Xnor => Some(0x21),
Self::Int => Some(0x22),
Self::Irt => Some(0x23),
Self::Hlt => Some(0x24),
Self::AddI => Some(0x25),
Self::SubI => Some(0x26),
Self::Segment => Some(0x27),
// Pseudo-instructions don't have opcode values
_ => None,
}
}
#[must_use]
pub const fn is_pseudo_instruction(&self) -> bool {
matches!(
self,
Self::Db
| Self::Dh
| Self::Dw
| Self::Resb
| Self::Resh
| Self::Resw
| Self::Push
| Self::Pop
| Self::Lwi
)
}
}
-407
View File
@@ -1,407 +0,0 @@
use std::path::{Path, PathBuf};
use crate::assembler::TokenType;
use crate::{assembler::AssembleError, expect_token, expect_type, node};
use crate::assembler::model::{Node, Opcode, Token};
use common::prelude::*;
pub struct Parser {
tokens: Vec<Token>,
nodes: Vec<Node>,
}
#[derive(Debug)]
pub struct Program {
pub nodes: Vec<Node>,
}
impl Program {
#[must_use]
pub const fn new() -> Self {
Self { nodes: vec![] }
}
pub fn add_module(&mut self, module: Vec<Node>) {
self.nodes.extend(module);
}
pub fn parser(&mut self) -> Parser {
Parser {
tokens: vec![],
nodes: self.nodes.clone(),
}
}
}
impl Default for Program {
fn default() -> Self {
Self::new()
}
}
impl Parser {
pub fn parse_nodes(tokens: Vec<Token>) -> Result<Vec<Node>, AssembleError> {
let mut self_ = Self {
tokens: tokens.into_iter().rev().collect(),
nodes: vec![],
};
while !self_.tokens.is_empty() {
let ins = self_.parse_instruction()?;
self_.nodes.push(ins);
}
Ok(self_.nodes.clone())
}
pub fn get_dependencies(
nodes: &Vec<Node>,
source_path: &Path,
) -> Result<Vec<PathBuf>, AssembleError> {
let mut dependencies = Vec::new();
// Get the parent directory of the source file to use as the base directory
let base_dir = source_path
.parent()
.ok_or_else(|| AssembleError::InvalidFile(source_path.to_path_buf()))?;
for node in nodes {
if node.opcode() == Opcode::Include {
let path_str = expect_token!(
node.args().get(1).ok_or(AssembleError::Generic)?,
StringLit
)?;
let path = PathBuf::from(path_str);
// If the path is not absolute, make it relative to the base directory
let full_path = if path.is_absolute() {
path
} else {
base_dir.join(path)
};
dependencies.push(full_path);
}
}
Ok(dependencies)
}
#[expect(clippy::too_many_lines, clippy::cognitive_complexity)]
fn parse_instruction(&mut self) -> Result<Node, AssembleError> {
if self.tokens.is_empty() {
unreachable!();
}
// check if the Node starts with a label
let label = expect_token!(self.peek_next()?, Symbol).ok();
if label.is_some() {
self.tokens.pop();
}
let opcode = expect_token!(self.next()?, Opcode)?;
let args: Vec<Token>;
#[allow(clippy::match_same_arms)]
match opcode {
// R-type instructions
Opcode::Mov | Opcode::Movs => {
let reg1 = expect_type!(self.next()?, Register, Symbol)?;
let reg2 = expect_type!(self.next()?, Register, Symbol)?;
args = vec![reg1, reg2];
}
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw => {
let base = expect_type!(self.next()?, Register, Symbol)?;
let dest = expect_type!(self.next()?, Register)?;
let offset = match self.peek_next() {
Ok(next) if expect_type!(next.clone(), Immediate).is_ok() => {
self.next()?
}
_ => Token::Immediate(0),
};
args = vec![base, dest, offset];
}
Opcode::Stb | Opcode::Sth | Opcode::Stw => {
let base = expect_type!(self.next()?, Register)?;
let dest = expect_type!(self.next()?, Register, Symbol)?;
let offset = match self.peek_next() {
Ok(next) if expect_type!(next.clone(), Immediate).is_ok() => {
self.next()?
}
_ => Token::Immediate(0),
};
args = vec![base, dest, offset];
}
Opcode::Add
| Opcode::Sub
| Opcode::And
| Opcode::Or
| Opcode::Xor
| Opcode::Nand
| Opcode::Nor
| Opcode::Xnor => {
let src1 = expect_type!(self.next()?, Register, Symbol)?;
let src2 = expect_type!(self.next()?, Register, Symbol)?;
let dest = expect_type!(self.next()?, Register, Symbol)?;
args = vec![src1, src2, dest];
}
Opcode::Not | Opcode::Cmp => {
let src = expect_type!(self.next()?, Register, Symbol)?;
let dest = expect_type!(self.next()?, Register, Symbol)?;
args = vec![src, dest];
}
Opcode::Shl | Opcode::Shr => {
let src = expect_type!(self.next()?, Register, Symbol)?;
// First operand after src: could be immediate or register
let first = self.next()?;
let (r_shamt, i_shamt) = match first {
Token::Register(_) => (
first,
if let Ok(tok) = self.peek_next() {
if expect_type!(tok, Immediate).is_ok() {
self.next()?
} else {
Token::Immediate(0)
}
} else {
Token::Immediate(0)
},
),
Token::Immediate(_) => (Token::Register(Register::Zero), first),
_ => {
return Err(AssembleError::UnexpectedToken(
first,
TokenType::Immediate,
));
}
};
let dest = if let Ok(tok) = self.peek_next() {
if expect_type!(tok, Register).is_ok() {
self.next()?
} else {
src.clone() // Default to src if no dest specified
}
} else {
src.clone() // Default to src if no dest specified
};
args = vec![src, r_shamt, i_shamt, dest];
}
Opcode::Inc | Opcode::Dec => {
let reg = expect_type!(self.next()?, Register, Symbol)?;
args = vec![reg];
}
Opcode::Include => {
let mod_name = expect_type!(self.next()?, Symbol)?;
let path = expect_type!(self.next()?, StringLit)?;
args = vec![mod_name, path];
}
// J-type instructions
Opcode::Jmp
| Opcode::Jeq
| Opcode::Jne
| Opcode::Jgt
| Opcode::Jge
| Opcode::Jlt
| Opcode::Jle => {
let imm = expect_type!(self.next()?, Immediate, Symbol)?;
let offset = match self.peek_next() {
Ok(token) => {
if expect_type!(token, Register).is_ok() {
self.next()?
} else {
Token::Register(Register::Zero)
}
}
Err(_) => Token::Register(Register::Zero),
};
args = vec![imm, offset];
}
Opcode::Call => {
let addr = expect_type!(self.next()?, Symbol)?;
args = vec![addr];
}
// I-type instructions
Opcode::Lui | Opcode::Lli | Opcode::Lwi => {
let imm = expect_type!(self.next()?, Immediate, Symbol)?;
let reg = expect_type!(self.next()?, Register)?;
args = vec![imm, reg];
}
// Immediate Arithmetic
Opcode::AddI | Opcode::SubI => {
let reg = expect_type!(self.next()?, Register)?;
let imm = expect_type!(self.next()?, Immediate)?;
let reg2 = if expect_type!(self.peek_next()?, Register).is_ok() {
self.next()?
} else {
reg.clone()
};
args = vec![reg, imm, reg2];
}
// D-type pseudoinstructions (data definition)
Opcode::Resb | Opcode::Resh | Opcode::Resw => {
let name = expect_type!(self.next()?, Symbol)?;
let num = expect_type!(self.next()?, Immediate)?;
args = vec![name, num];
}
Opcode::Db | Opcode::Dh | Opcode::Dw => {
args = self.parse_data_definition(opcode)?;
}
// E-type pseudoinstructions (stack operations)
Opcode::Push | Opcode::Pop => {
let reg = expect_type!(self.next()?, Register, Symbol)?;
args = vec![reg];
}
Opcode::Pusha | Opcode::Popa => {
let count =
expect_type!(self.next()?, Immediate).unwrap_or(Token::Immediate(8));
args = vec![count];
}
// Special instructions
Opcode::Int => {
let val = expect_type!(self.next()?, Immediate)?;
args = vec![val];
}
// Instructions with no arguments
Opcode::Hlt | Opcode::Nop | Opcode::Irt | Opcode::Return => {
args = vec![];
}
Opcode::Data | Opcode::Segment => {
return Err(AssembleError::Generic);
}
}
Ok(node!(label, opcode, args: args))
}
fn parse_data_definition(
&mut self,
opcode: Opcode,
) -> Result<Vec<Token>, AssembleError> {
let mut values = Vec::new();
let name = expect_type!(self.next()?, Symbol)?;
values.push(name);
match opcode {
Opcode::Db => {
// db can take string literals or u8 immediates
while !self.tokens.is_empty() {
let token = self
.tokens
.last()
.expect("Expected a token for data definition, but found none");
match token {
Token::StringLit(_) => {
values.push(self.tokens.pop().expect(
"Expected a token for data definition, but found none",
));
}
Token::Immediate(val) if u8::try_from(*val).is_ok() => {
values.push(self.tokens.pop().expect(
"Expected a token for data definition, but found none",
));
}
_ => break,
}
}
}
Opcode::Dh => {
// dh can take u16 immediates
while !self.tokens.is_empty() {
let token = self
.tokens
.last()
.expect("Expected a token for data definition, but found none");
match token {
Token::StringLit(_) => {
values.push(self.tokens.pop().expect(
"Expected a token for data definition, but found none",
));
}
Token::Immediate(val) if u16::try_from(*val).is_ok() => {
values.push(self.tokens.pop().expect(
"Expected a token for data definition, but found none",
));
}
_ => break,
}
}
}
Opcode::Dw => {
// dw can take u32 immediates
while !self.tokens.is_empty() {
match self
.tokens
.last()
.expect("Expected a token for data definition, but found none")
{
Token::StringLit(_) => {
values.push(self.tokens.pop().expect(
"Expected a token for data definition, but found none",
));
}
Token::Immediate(val) => {
values.push(self.tokens.pop().expect(
"Expected a token for data definition, but found none",
));
}
_ => break,
}
}
}
_ => unreachable!(),
}
Ok(values)
}
fn next(&mut self) -> Result<Token, AssembleError> {
if self.tokens.is_empty() {
Err(AssembleError::UnexpectedEof)
} else {
Ok(self
.tokens
.pop()
.expect("tokens vector was unexpectedly empty in next()"))
}
}
fn peek_next(&self) -> Result<Token, AssembleError> {
if self.tokens.is_empty() {
Err(AssembleError::UnexpectedEof)
} else {
Ok(self
.tokens
.last()
.expect("peek_next called on empty tokens vector")
.clone())
}
}
}
-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(())
}
+27
View File
@@ -0,0 +1,27 @@
//! 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()),
}
}
}
+82
View File
@@ -0,0 +1,82 @@
//! 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,
}
impl AssembleError {
#[must_use]
pub const fn new_source_error(
source_info: SourceInfo,
kind: AssembleErrorKind,
) -> Self {
Self {
source_info: Some(source_info),
kind,
}
}
#[must_use]
pub const fn new_other_error(kind: AssembleErrorKind) -> Self {
Self {
source_info: None,
kind,
}
}
}
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}")?;
}
write!(f, "{}", self.kind)?;
Ok(())
}
}
/// Marker trait.
impl std::error::Error for AssembleError {}
/// Different types of errors that may occur when assembling a set of input source files.
#[non_exhaustive]
#[derive(Debug)]
pub enum AssembleErrorKind {
/// Usually unexpected I/O errors. Not normally recoverable.
IO(std::io::Error),
/// Errors emitted from the [`Tokeniser`].
Tokenise(TokeniserError),
}
impl Display for AssembleErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Tokenise(why) => write!(f, "tokeniser error: {why}"),
_ => write!(
f,
"unhandled error type in Display implementation! See error.rs!"
),
}
}
}
impl From<std::io::Error> for AssembleErrorKind {
fn from(err: std::io::Error) -> Self {
Self::IO(err)
}
}
pub mod conversions;
+7
View File
@@ -0,0 +1,7 @@
use crate::error::AssembleError;
impl From<std::io::Error> for AssembleError {
fn from(err: std::io::Error) -> Self {
Self::new_other_error(err.into())
}
}
+10 -27
View File
@@ -12,39 +12,22 @@
clippy::match_wildcard_for_single_variants clippy::match_wildcard_for_single_variants
)] )]
pub mod assembler; pub mod args;
pub mod image_builder; pub mod image_builder;
pub mod tooling; // pub mod tooling;
pub mod context;
pub mod error;
pub mod model;
pub mod source;
pub mod symtab;
mod util; mod util;
pub mod prelude { pub mod prelude {
pub use crate::assembler::CompilerEngine;
pub use crate::image_builder; pub use crate::image_builder;
pub use crate::tooling::brainf; // pub use crate::tooling::brainf;
pub use crate::tooling::project; // pub use crate::tooling::project;
} }
use std::{fs, path::Path};
use num_cpus as _; use num_cpus as _;
use threadpool as _; use threadpool as _;
use crate::prelude::CompilerEngine;
pub fn assemble_file(input: &str, output: &str) -> Result<(), std::io::Error> {
let mut engine = CompilerEngine::new();
engine.start_compilation(Path::new(input));
let result = engine.wait_for_result().expect("assembler failed.");
let buffer: Vec<u8> = result
.iter()
.flat_map(|instruction| instruction.encode().to_be_bytes())
.collect();
if let Err(e) = fs::write(output, buffer) {
eprintln!("Failed to write to output file: {e}");
std::process::exit(1);
}
Ok(())
}
+49 -37
View File
@@ -2,50 +2,62 @@ use common as _;
use num_cpus as _; use num_cpus as _;
use threadpool as _; use threadpool as _;
use assembler::{ // use clap::Parser;
assemble_file, // use std::{fs, io::Write, path::PathBuf};
prelude::*,
tooling::{brainf, project},
};
use std::{fs, io::Write, path::PathBuf};
fn main() { fn main() {
// Parse command line arguments // // Parse command line arguments
let args: Vec<String> = std::env::args().collect(); // let args: Vec<String> = std::env::args().collect();
if args.len() == 2 && args[1] == "init" { // let _clap_args = assembler::args::Args::parse();
project::tool_libcreate();
std::process::exit(0);
}
if args.len() == 2 && args[1] == "brainf" { // if args.len() == 2 && args[1] == "init" {
let src = PathBuf::from("brainf.bf"); // // project::tool_libcreate();
let result = brainf::build(&src); // std::process::exit(0);
// }
let mut file = match fs::File::create("brainf.dsb") { // if args.len() == 2 && args[1] == "brainf" {
Err(e) => { // let src = PathBuf::from("brainf.bf");
eprintln!("Failed to create output file: {e}"); // // let result = brainf::build(&src);
std::process::exit(1);
}
Ok(file) => file,
};
for instruction in result { // let mut file = match fs::File::create("brainf.dsb") {
if let Err(e) = file.write(&instruction.encode().to_be_bytes()) { // Err(e) => {
eprintln!("Failed to write to output file: {e}"); // eprintln!("Failed to create output file: {e}");
std::process::exit(1); // std::process::exit(1);
} // }
} // Ok(file) => file,
// };
std::process::exit(0); // // 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);
// // }
// // }
if args.len() != 5 || args[1] != "-i" || args[3] != "-o" { // std::process::exit(0);
eprintln!("Usage: {} -i input_path -o output_path", args[0]); // }
std::process::exit(1);
}
let input_path = &args[2]; // if args.len() != 5 || args[1] != "-i" || args[3] != "-o" {
let output_path = &args[4]; // eprintln!("Usage: {} -i input_path -o output_path", args[0]);
assemble_file(input_path, output_path).unwrap(); // 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;
+68
View File
@@ -0,0 +1,68 @@
//! 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};
use uuid::Uuid;
use crate::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 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 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 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)]
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>>(name: String, path: P) -> Self {
Self {
name,
path: path.as_ref().to_path_buf(),
id: ModuleId(Uuid::new_v4()),
}
}
}
+42
View File
@@ -0,0 +1,42 @@
//! This module contains the code for the module registry. This is a singleton storing all
//! the modules being assembled.
use std::collections::HashMap;
use super::module::{Module, ModuleId};
/// Stores all the [`Module`]'s to be assembled.
pub struct ModuleRegistry {
modules: HashMap<ModuleId, 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<&Module> {
self.modules.get(module_id)
}
/// Adds a [`Module`] and returns its [`ModuleId`].
pub fn add(&mut self, module: Module) -> ModuleId {
let id = module.id;
self.modules.insert(id, module);
id
}
/// Returns an iterator of modules.
pub fn modules(&self) -> impl Iterator<Item = &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,
}
+18
View File
@@ -0,0 +1,18 @@
//! This module contains anything within the first stage of assembly, i.e. the
//! tokenisation stage, or utility functions for reading input files.
use std::path::Path;
use crate::error::AssembleError;
pub mod source_info;
pub mod token;
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)?)
}
+25
View File
@@ -0,0 +1,25 @@
//! 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;
use crate::model::module::Module;
/// Information on where the token is within the source.
#[derive(Debug)]
pub struct SourceInfo {
/// The line number within the source file underpinned by `module_id`.
pub line_no: usize,
pub module: 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.name)
}
}
+106
View File
@@ -0,0 +1,106 @@
//! 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 crate::source::source_info::SourceInfo;
#[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),
/// Assembly instruction (e.g., `add`, `jmp`, `nop`).
Instruction(InstructionToken),
/// Label definition (e.g., `loop_start:`).
Label(LabelToken),
/// Assembler directive (e.g., `.global`, `.section`, `.dw`).
Directive(DirectiveToken),
/// End of line.
Newline,
/// End of file.
Eof,
}
#[derive(Debug)]
pub struct Token {
/// The type of the token.
token_type: TokenType,
/// Where in the source code is this [`Token`]?
source_info: SourceInfo,
}
#[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 name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct InstructionToken {
pub mnemonic: String,
}
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(mnemonic: String, source_info: SourceInfo) -> Self {
Self::new(
TokenType::Instruction(InstructionToken { mnemonic }),
source_info,
)
}
#[must_use]
pub const fn register(name: String, source_info: SourceInfo) -> Self {
Self::new(TokenType::Register(RegisterToken { name }), 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,
)
}
}
+7
View File
@@ -0,0 +1,7 @@
//! This file contains the [`Tokeniser`], which consumes a [`Vec`] of input bytes and
//! outputs a [`Vec<Token>`].
/// Consumes a [`Vec<u8>`] and outputs a [`Vec`] of [Token]'s.
pub struct Tokeniser {}
pub mod error;
+10
View File
@@ -0,0 +1,10 @@
//! This module contains the error types for the tokeniser.
#[derive(Debug)]
pub enum TokeniserError {}
impl std::fmt::Display for TokeniserError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TODO!!!!!!")
}
}
+121
View File
@@ -0,0 +1,121 @@
//! 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(AssembleError::new_other_error(
crate::error::AssembleErrorKind::IO(std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
format!("Symbol '{name}' already defined in module"),
)),
));
}
// 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(AssembleError::new_other_error(
crate::error::AssembleErrorKind::IO(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Symbol not found",
)),
))
}
}
}
impl Default for SymbolTable {
fn default() -> Self {
Self::new()
}
}
+1
View File
@@ -2,6 +2,7 @@
#![allow(unused)] #![allow(unused)]
use std::{fmt, sync::mpsc::Sender}; use std::{fmt, sync::mpsc::Sender};
#[derive(Debug, PartialEq, Eq)]
pub struct Logger {} pub struct Logger {}
impl Logger { impl Logger {
-1
View File
@@ -1 +0,0 @@
disallowed-types = ["std::collections::HashMap", "std::collections::HashSet"]
+1
View File
@@ -5,3 +5,4 @@ edition.workspace = true
authors.workspace = true authors.workspace = true
[dependencies] [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);
}
+12 -10
View File
@@ -10,9 +10,7 @@ pub enum Interrupt {
pub type Address = u32; pub type Address = u32;
impl Interrupt { impl Interrupt {
// someone tell clippy to stfu. const fn as_u8(self) -> u8 {
#[allow(clippy::must_use_candidate)]
pub const fn as_u8(self) -> u8 {
match self { match self {
Self::Breakpoint => 0, Self::Breakpoint => 0,
Self::HardFault => 1, Self::HardFault => 1,
@@ -40,7 +38,7 @@ pub enum InstructionType {
Immediate, Immediate,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive] #[non_exhaustive]
pub enum Register { pub enum Register {
// general purpose registers // general purpose registers
@@ -69,9 +67,7 @@ pub enum Register {
Idr, Idr,
Mmr, Mmr,
Zero, Zero,
NoReg,
#[default]
Null, // Invalid - Triggers a fault if accessed
// system registers - can't be written to by instructions. // system registers - can't be written to by instructions.
Mar, Mar,
@@ -106,6 +102,12 @@ impl Register {
} }
} }
impl Default for Register {
fn default() -> Self {
Self::NoReg
}
}
impl TryFrom<u8> for Register { impl TryFrom<u8> for Register {
type Error = RegisterParseError; type Error = RegisterParseError;
@@ -140,7 +142,7 @@ impl TryFrom<u8> for Register {
0x14 => Self::Idr, 0x14 => Self::Idr,
0x15 => Self::Mmr, 0x15 => Self::Mmr,
0x16 => Self::Zero, 0x16 => Self::Zero,
0x17 => Self::Null, 0x17 => Self::NoReg,
0x18 => Self::Mar, 0x18 => Self::Mar,
0x19 => Self::Mdr, 0x19 => Self::Mdr,
0x1A => Self::Sts, 0x1A => Self::Sts,
@@ -179,7 +181,7 @@ impl TryFrom<&str> for Register {
"idr" => Ok(Self::Idr), "idr" => Ok(Self::Idr),
"mmr" => Ok(Self::Mmr), "mmr" => Ok(Self::Mmr),
"zero" => Ok(Self::Zero), "zero" => Ok(Self::Zero),
"null" => Ok(Self::Null), "null" => Ok(Self::NoReg),
"pcx" => Ok(Self::Pcx), "pcx" => Ok(Self::Pcx),
_ => Err(RegisterParseError::InvalidName(value.to_string())), _ => Err(RegisterParseError::InvalidName(value.to_string())),
} }
@@ -212,7 +214,7 @@ impl std::fmt::Display for Register {
Self::Idr => write!(f, "idr"), Self::Idr => write!(f, "idr"),
Self::Mmr => write!(f, "mmr"), Self::Mmr => write!(f, "mmr"),
Self::Zero => write!(f, "zero"), Self::Zero => write!(f, "zero"),
Self::Null => write!(f, "null"), Self::NoReg => write!(f, "noreg"),
Self::Mar => write!(f, "mar"), Self::Mar => write!(f, "mar"),
Self::Mdr => write!(f, "mdr"), Self::Mdr => write!(f, "mdr"),
Self::Sts => write!(f, "sts"), Self::Sts => write!(f, "sts"),
+6 -8
View File
@@ -8,9 +8,9 @@ pub trait Encode {
/// Encodes a zero argument instruction. /// Encodes a zero argument instruction.
fn encode_no_args(opcode: u8) -> u32 { fn encode_no_args(opcode: u8) -> u32 {
let opcode = u32::from(opcode); let opcode = u32::from(opcode);
let sr1 = Register::Null as u32; let sr1 = Register::NoReg as u32;
let sr2 = Register::Null as u32; let sr2 = Register::NoReg as u32;
let dr = Register::Null as u32; let dr = Register::NoReg as u32;
let shamt = 0; let shamt = 0;
(opcode << 26) | (sr1 << 21) | (sr2 << 16) | (dr << 11) | (shamt << 6) (opcode << 26) | (sr1 << 21) | (sr2 << 16) | (dr << 11) | (shamt << 6)
@@ -54,14 +54,12 @@ impl Encode for Instruction {
], ],
no_args: [Nop, IntReturn, Halt], no_args: [Nop, IntReturn, Halt],
special: [ special: [
Self::Interrupt(_) => todo!(),
Self::Data(data) => data, Self::Data(data) => data,
Self::Interrupt(interrupt) => {
let opcode = u32::from(self.opcode());
(opcode << 26) | u32::from(interrupt.as_u8())
},
Self::Segment(segment) => { Self::Segment(segment) => {
let opcode = u32::from(self.opcode()); let opcode = u32::from(self.opcode());
(opcode << 26) | u32::from(segment as u8) let segment = segment as u8;
(opcode << 26) | u32::from(segment)
} }
] ]
) )
+4 -4
View File
@@ -2,7 +2,7 @@ use crate::prelude::*;
#[test] #[test]
fn test_encode_nop() { fn test_encode_nop() {
let no_reg = Register::Null as u32; let no_reg = Register::NoReg as u32;
let no_op = u32::from(Instruction::Nop.opcode()); let no_op = u32::from(Instruction::Nop.opcode());
let expected = (no_op << 26) | (no_reg << 21) | (no_reg << 16) | (no_reg << 11); let expected = (no_op << 26) | (no_reg << 21) | (no_reg << 16) | (no_reg << 11);
@@ -15,7 +15,7 @@ fn test_encode_nop() {
fn test_encode_mov() { fn test_encode_mov() {
let rg0 = Register::Rg0 as u32; let rg0 = Register::Rg0 as u32;
let rg1 = Register::Rg1 as u32; let rg1 = Register::Rg1 as u32;
let no_reg = Register::Null as u32; let no_reg = Register::NoReg as u32;
let instruction = Instruction::Mov(RTypeArgs::new( let instruction = Instruction::Mov(RTypeArgs::new(
Some(Register::Rg0), Some(Register::Rg0),
@@ -53,7 +53,7 @@ fn test_encode_load_byte() {
#[test] #[test]
fn test_encode_shift_left_shamt() { fn test_encode_shift_left_shamt() {
let rg0 = Register::Rg0 as u32; let rg0 = Register::Rg0 as u32;
let no_reg = Register::Null as u32; let no_reg = Register::NoReg as u32;
let shift_amount = 5; let shift_amount = 5;
@@ -80,7 +80,7 @@ fn test_encode_shift_left_shamt() {
fn test_encode_shift_left_reg() { fn test_encode_shift_left_reg() {
let rg0 = Register::Rg0 as u32; let rg0 = Register::Rg0 as u32;
let rg1 = Register::Rg1 as u32; let rg1 = Register::Rg1 as u32;
let no_reg = Register::Null as u32; let no_reg = Register::NoReg as u32;
let instruction = Instruction::ShiftLeft(RTypeArgs::new( let instruction = Instruction::ShiftLeft(RTypeArgs::new(
Some(Register::Rg0), Some(Register::Rg0),
+1 -1
View File
@@ -12,8 +12,8 @@
clippy::match_wildcard_for_single_variants clippy::match_wildcard_for_single_variants
)] )]
pub mod elf;
pub mod instructions; pub mod instructions;
pub mod logging;
pub mod prelude { pub mod prelude {
//! A collection of types you should definitely import when working with this crate. //! 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}");
}
-10
View File
@@ -1,10 +0,0 @@
[package]
name = "compiler"
version.workspace = true
edition.workspace = true
authors.workspace = true
[dependencies]
chrono = "0.4.43"
common = { path = "../common" }
uuid = { version = "1.20.0", features = ["v4"] }
-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
-955
View File
@@ -1,955 +0,0 @@
use std::collections::HashMap;
use std::sync::atomic::AtomicU32;
use std::time::SystemTime;
use chrono::{DateTime, Local};
use super::registers::RegisterAllocator;
use crate::backend::dsa::instruction::{InsBlock as IB, Instruction as I, Label};
use crate::backend::dsa::registers::Register;
use crate::model::{
AssignmentOperator, BinaryOperator, Call, CompilerError, ConstExpr, Declaration,
Dependency, Expression, Number, Program, Statement, TypeId, UnaryOperator, Variable,
};
pub struct CodeGenerator {
ast: Program,
imports: HashMap<String, I>,
globals: HashMap<String, I>,
functions: Vec<IB>,
symbols: Vec<String>,
allocator: RegisterAllocator,
}
impl CodeGenerator {
pub fn new(ast: Program) -> Self {
CodeGenerator {
ast,
imports: HashMap::new(),
globals: HashMap::new(),
functions: Vec::new(),
symbols: Vec::new(),
allocator: RegisterAllocator::new(),
}
}
pub fn include(&mut self, name: impl Into<String>, path: impl Into<String>) {
let name = name.into();
self.imports.insert(name.clone(), I::include(name, path));
}
fn is_global(&self, name: &str) -> bool {
// Check if this variable is in the globals list
self.globals.contains_key(name)
}
pub fn generate(&mut self) -> Result<String, CompilerError> {
// always include the print library for debugging!
self.include("print", "./lib/io/print.dsa");
for block in self.ast.clone().declarations {
match block {
Declaration::Variable {
var: Variable { name, .. },
..
} => self.symbols.push(name),
Declaration::Function { name, .. } => self.symbols.push(name),
Declaration::Dependency(Dependency { name, .. }) => {
self.symbols.push(name)
}
Declaration::Struct { .. } => {} /* we can't do any code generation for
* a struct yet. we may need to later
* once these become class-like
* objects with implementations */
}
}
for block in self.ast.clone().declarations {
self.generate_block(block.clone())?;
}
let assembly = self.generate_layout()?;
Ok(assembly
.iter()
.map(|i| i.to_string())
.collect::<Vec<_>>()
.join("\n"))
}
fn generate_layout(&mut self) -> Result<IB, CompilerError> {
let datetime: DateTime<Local> = SystemTime::now().into();
let mut block = IB::new();
block.extend(vec![
I::global_comment(format!(
"GENERATED BY DSC COMPILER
Generated at {}",
datetime.format("%Y-%m-%d %H:%M:%S")
)),
I::Newline,
I::global_comment("Imports"),
]);
block.extend(self.imports.values().cloned().collect::<Vec<_>>());
block.extend(vec![
I::Newline,
I::global_comment("Globals & Reserved Memory"),
]);
block.extend(self.globals.values().cloned().collect::<Vec<_>>());
block.extend(vec![
I::Newline,
I::global_comment("Entry Point"),
I::db_word("stack", 0x10000),
I::db_string("message", "Process Exited with code:"),
// init function for stack setup.
I::label("_init"),
I::ldw_label("stack", Register::Bpr),
I::mov(Register::Bpr, Register::Spr),
I::push(Register::Zero),
I::call("main"),
I::call("print::print_newline"),
I::lwi_label("message", Register::Rg0),
I::push(Register::Rg0),
I::call("print::print"),
I::pop(Register::Zero),
I::call("print::print_hex_word"),
I::pop(Register::Zero),
I::Hlt,
I::Newline,
// default return block boilerplate
I::global_comment("Return"),
I::label("_ret"),
I::mov(Register::Bpr, Register::Spr),
I::pop(Register::Bpr),
I::Return,
]);
for function in self.functions.iter() {
block.extend(function.iter().cloned());
}
Ok(block)
}
fn generate_global(&mut self, name: &str, init: Option<ConstExpr>) {
let init = init.unwrap_or(ConstExpr::Number(0));
match init {
ConstExpr::Number(value) => {
self.globals
.insert(name.to_string(), I::db_word(name, value as u32));
}
ConstExpr::String(str) => {
self.globals
.insert(name.to_string(), I::db_string(name, str));
}
}
}
fn generate_block(&mut self, block: Declaration) -> Result<(), CompilerError> {
match block {
Declaration::Variable { var, init, .. } => {
self.generate_global(&var.name, init)
}
Declaration::Function {
name,
params,
body,
return_type,
} => {
let func = self.generate_function(&name, &params, &body, return_type);
self.functions.push(func);
}
Declaration::Dependency(Dependency { name, path }) => {
self.include(name, path);
}
Declaration::Struct { .. } => {} /* can't do any codegen for these yet,
* they're just types. */
};
Ok(())
}
// Example: Generate code for a function
fn generate_function(
&mut self,
name: &str,
params: &[Variable],
body: &[Statement],
return_type: TypeId,
) -> IB {
let mut code = IB::new();
// Reset allocator for new function
self.allocator.reset();
let fmtparams = params
.iter()
.map(|p| format!("{}: {}", p.name, p.type_id))
.collect::<Vec<String>>()
.join(", ");
code.extend(vec![
I::global_comment(format!("fn {name}({fmtparams}) -> {return_type}")),
I::label(name),
I::push(Register::Bpr),
I::mov(Register::Spr, Register::Bpr),
]);
// Allocate parameters to registers or stack locations
for (i, param) in params.iter().enumerate() {
let offset = 8 + (i as i32 * 4); // Parameters start at bpr+8
// Track that this parameter is at a stack location
let (reg, load_code) = self.allocator.alloc_var(&param.name).unwrap();
code.append(load_code);
code.push(I::ldw_reg_offset(Register::Bpr, reg, offset));
}
// Generate code for function body
for stmt in body {
let stmt_code = self.generate_statement(stmt, &mut code).unwrap();
code.append(stmt_code);
}
// automatically return at function end
if let Some(x) = code.iter().last()
&& let I::Jmp { target: Label(val) } = x
&& val == "_ret"
{
} else {
code.push(I::jmp("_ret"));
}
code.insert(0, I::Newline);
code
}
// Example: Generate code for a statement
fn generate_statement(
&mut self,
stmt: &Statement,
func_body: &mut IB,
) -> Result<IB, CompilerError> {
let mut code = IB::new();
match stmt {
Statement::Declaration { var, value } => {
if let Some(expr) = value {
// Evaluate expression
let (result_reg, expr_code) =
self.generate_expression(expr, true, func_body)?;
code.append(expr_code);
// Store result in variable
let store_code = self.allocator.store_var(&var.name, &result_reg);
code.append(store_code);
// Free temporary register
self.allocator.free_temp(result_reg);
} else {
// Just declaring variable without initialization
self.allocator.alloc_var(&var.name)?;
}
}
Statement::Break => unimplemented!("need scope tracking first!"),
Statement::Continue => unimplemented!("need scope tracking first!"),
Statement::Defer(_func) => unimplemented!("we need scope tracking first!"),
Statement::PtrWrite { ptr, value } => {
let (result_reg, expr_code) =
self.generate_expression(value, true, func_body)?;
code.append(expr_code);
let (ptr_reg, ptr_code) =
self.generate_expression(ptr, true, func_body)?;
code.append(ptr_code);
code.push(I::stw_reg(result_reg, ptr_reg));
self.allocator.free_temp(result_reg);
self.allocator.free_temp(ptr_reg);
}
Statement::Assign {
varname,
value,
operator,
} => {
// Evaluate expression
let (result_reg, expr_code) =
self.generate_expression(value, true, func_body)?;
code.append(expr_code);
if *operator == AssignmentOperator::Assign {
// Check if this is a global variable
if self.is_global(varname) {
// Store to global label
code.push(I::stw_label(result_reg, varname.clone()))
} else {
// Store result in local variable
let store_code = self.allocator.store_var(varname, &result_reg);
code.append(store_code);
}
// Free temporary register
self.allocator.free_temp(result_reg);
return Ok(code);
}
// for more complex assignment cases we need an intermediate register.
let (temp_reg, temp_code) = self.allocator.alloc_temp()?;
code.append(temp_code);
// fetch the value of the variable
let var_reg = if self.is_global(varname) {
let instruction = I::ldw_label(varname.clone(), temp_reg);
code.push(instruction);
temp_reg
} else {
let (rg, block) = self.allocator.load_var(varname)?;
code.append(block);
rg
};
let assign_code = match operator {
AssignmentOperator::Assign => {
unreachable!("assignment was already checked earlier.")
}
AssignmentOperator::AddAssign => {
I::add(var_reg, result_reg, temp_reg)
}
AssignmentOperator::SubAssign => {
I::sub(var_reg, result_reg, temp_reg)
}
AssignmentOperator::MulAssign => {
return Err(CompilerError::Unimplemented(
"TODO: implement multiplication for assignment".to_string(),
));
}
AssignmentOperator::DivAssign => {
return Err(CompilerError::Unimplemented(
"TODO: write proper div function for DSA".to_string(),
));
}
AssignmentOperator::ModAssign => {
return Err(CompilerError::Unimplemented(
"TODO: write proper mod function for DSA".to_string(),
));
}
AssignmentOperator::AndAssign => {
I::and(var_reg, result_reg, temp_reg)
}
AssignmentOperator::OrAssign => I::or(var_reg, result_reg, temp_reg),
AssignmentOperator::XorAssign => {
I::xor(var_reg, result_reg, temp_reg)
}
AssignmentOperator::LeftShiftAssign => {
// this is only useful if we optimise out the register allocation
// inside value.
// if let Expression::Number { value, .. } = *value {
// I::shl(var_reg, value, temp_reg)
// }
I::shl(var_reg, result_reg, 0, temp_reg)
}
AssignmentOperator::RightShiftAssign => {
// this is only useful if we optimise out the register allocation
// if let Expression::Number { value, .. } = *value {
// I::shr(var_reg, value, temp_reg)
// }
I::shr(var_reg, result_reg, 0, temp_reg)
}
};
code.push(assign_code);
// Check if this is a global variable
if self.is_global(varname) {
// Store to global label
code.push(I::stw_label(temp_reg, varname.clone()))
} else {
// Store result in local variable
let store_code = self.allocator.store_var(varname, &temp_reg);
code.append(store_code);
}
self.allocator.free_temp(result_reg);
self.allocator.free_temp(temp_reg);
}
Statement::Return(expr) => {
if let Some(e) = expr {
let (result_reg, expr_code) =
self.generate_expression(e, true, func_body)?;
code.append(expr_code);
code.push(I::stw_reg_offset(result_reg, Register::Bpr, 8));
code.push(I::jmp("_ret"));
self.allocator.free_temp(result_reg);
}
}
Statement::If {
condition,
then_stmt,
else_stmt,
} => {
// Generate condition
let (cond_reg, cond_code) =
self.generate_expression(condition, true, func_body)?;
code.append(cond_code);
// Compare with zero
code.push(I::cmp(cond_reg, Register::Zero));
self.allocator.free_temp(cond_reg);
// Generate unique labels
let then_label = format!("_then_{}", self.get_unique_label());
let else_label = format!("_else_{}", self.get_unique_label());
let end_label = format!("_end_{}", self.get_unique_label());
// Jump to else if condition is false (equal to zero)
code.push(I::jeq(else_label.clone()));
// Then block
code.push(I::label(then_label));
for s in then_stmt {
code.append(self.generate_statement(s, func_body)?);
}
if then_stmt.is_empty() {
code.push(I::Nop);
}
code.push(I::jmp(end_label.clone()));
// Else block
code.push(I::label(else_label));
for s in else_stmt {
code.append(self.generate_statement(s, func_body)?);
}
if else_stmt.is_empty() {
code.push(I::Nop);
}
code.push(I::label(end_label));
}
Statement::While { condition, body } => {
let loop_start = format!("_while_start_{}", self.get_unique_label());
let loop_end = format!("_while_end_{}", self.get_unique_label());
code.push(I::label(&loop_start));
// Generate condition
let (cond_reg, cond_code) =
self.generate_expression(condition, true, func_body)?;
code.append(cond_code);
code.push(I::cmp(cond_reg, Register::Zero));
self.allocator.free_temp(cond_reg);
code.push(I::jeq(loop_end.clone()));
// Loop body
for s in body {
code.append(self.generate_statement(s, func_body)?);
}
code.push(I::jmp(loop_start));
code.push(I::label(loop_end));
}
Statement::Loop(body) => {
let loop_start = format!("_loop_start_{}", self.get_unique_label());
code.push(I::label(&loop_start));
for s in body {
code.append(self.generate_statement(s, func_body)?);
}
code.push(I::jmp(loop_start));
}
Statement::Expression { expr } => {
let (result_reg, expr_code) =
self.generate_expression(expr, false, func_body)?;
code.append(expr_code);
self.allocator.free_temp(result_reg);
}
Statement::Block(statements) => {
for s in statements {
code.append(self.generate_statement(s, func_body)?);
}
}
}
Ok(code)
}
// Example: Generate code for an expression
// Returns (register containing result, assembly code)
fn generate_expression(
&mut self,
expr: &Expression,
use_result: bool,
func_body: &mut IB,
) -> Result<(Register, IB), CompilerError> {
let mut code = IB::new();
match expr {
Expression::Empty => Ok((Register::Null, code)),
Expression::Number(n) => match n {
Number::Signed(value, _) => {
let (reg, alloc_code) = self.allocator.alloc_temp()?;
code.append(alloc_code);
// Load immediate value
code.push(I::lwi(*value as u32, reg));
Ok((reg, code))
}
Number::Unsigned(value, _) => {
let (reg, alloc_code) = self.allocator.alloc_temp()?;
code.append(alloc_code);
// Load immediate value
code.push(I::lwi(*value as u32, reg));
Ok((reg, code))
}
},
Expression::CharLiteral(value) => {
let (reg, alloc_code) = self.allocator.alloc_temp()?;
code.append(alloc_code);
// Load immediate value
code.push(I::comment(format!("char literal '{value}'")));
code.push(I::lwi(*value as u32, reg));
Ok((reg, code))
}
Expression::StringLiteral(value) => {
let (reg, alloc_code) = self.allocator.alloc_temp()?;
code.append(alloc_code);
// write string into memory
let uuid = self.get_unique_label();
func_body.insert(0, I::db_string(format!("str_{uuid}"), value));
// Load pointer to string
code.push(I::lwi_label(format!("str_{uuid}"), reg));
Ok((reg, code))
}
Expression::ArrayLiteral { elements, type_id } => todo!(),
Expression::StructLiteral {
name,
fields,
type_id,
} => todo!(),
Expression::Variable { name, .. } => {
if self.is_global(&name.name) {
// Allocate a temporary register for the global
let (reg, alloc_code) = self.allocator.alloc_temp()?;
code.append(alloc_code);
// Load from global label
code.push(I::ldw_label(name.name.clone(), reg));
Ok((reg, code))
} else {
// Local variable - use existing allocator logic
let (reg, load_code) = self.allocator.load_var(&name.name)?;
code.append(load_code);
Ok((reg, code))
}
}
Expression::Binary {
op, left, right, ..
} => {
// Evaluate left operand
let (left_reg, left_code) =
self.generate_expression(left, true, func_body)?;
code.append(left_code);
// Evaluate right operand
let (right_reg, right_code) =
self.generate_expression(right, true, func_body)?;
code.append(right_code);
// Allocate result register
let (result_reg, result_alloc) = self.allocator.alloc_temp()?;
code.append(result_alloc);
// Generate operation
match op {
BinaryOperator::Add => {
code.push(I::add(left_reg, right_reg, result_reg));
}
BinaryOperator::Sub => {
code.push(I::sub(left_reg, right_reg, result_reg));
}
BinaryOperator::Mul => {
self.include("maths", "./lib/maths/core.dsa");
// Call multiply function
code.push(I::push(right_reg));
code.push(I::push(left_reg));
code.push(I::call("maths::multiply"));
code.push(I::pop(result_reg));
code.push(I::pop(Register::Zero));
}
BinaryOperator::Div => {
return Err(CompilerError::Unimplemented(
"TODO: write proper div function for DSA".to_string(),
));
// self.include("maths", "./lib/maths/core.dsa");
// // Call divide function
// code.push(format!("\tpush {}", right_reg));
// code.push(format!("\tpush {}", left_reg));
// code.push("\tcall maths::divide".to_string());
// code.push(format!("\tpop {}", result_reg));
// code.push("\tpop zero".to_string());
}
BinaryOperator::Mod => {
return Err(CompilerError::Unimplemented(
"TODO: write proper mod function for DSA".to_string(),
));
// self.include("maths", "./lib/maths/core.dsa");
// // Call modulo function
// code.push(format!("\tpush {}", right_reg));
// code.push(format!("\tpush {}", left_reg));
// code.push("\tcall maths::modulo".to_string());
// code.push(format!("\tpop {}", result_reg));
// code.push("\tpop zero".to_string());
}
BinaryOperator::BitwiseAnd => {
code.push(I::and(left_reg, right_reg, result_reg));
}
BinaryOperator::BitwiseOr => {
code.push(I::or(left_reg, right_reg, result_reg));
}
BinaryOperator::BitwiseXor => {
code.push(I::xor(left_reg, right_reg, result_reg));
}
BinaryOperator::LogicalAnd => {
return Err(CompilerError::Unimplemented(
"assembler/ISA does not yet support logical and!".to_string(),
));
}
BinaryOperator::LogicalOr => {
return Err(CompilerError::Unimplemented(
"assembler/ISA does not yet support logical or!".to_string(),
));
}
BinaryOperator::LeftShift => {
code.push(I::shl(left_reg, right_reg, 0, result_reg));
}
BinaryOperator::RightShift => {
code.push(I::shr(left_reg, right_reg, 0, result_reg));
}
// Comparison operators - return 1 (true) or 0 (false)
BinaryOperator::Equal => {
code.push(I::cmp(left_reg, right_reg));
code.push(I::lwi(1, result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(I::jeq(end_label.clone()));
code.push(I::lwi(0, result_reg));
code.push(I::label(end_label));
}
BinaryOperator::NotEqual => {
code.push(I::cmp(left_reg, right_reg));
code.push(I::lwi(1, result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(I::Jne {
target: Label(end_label.clone()),
});
code.push(I::lwi(0, result_reg));
code.push(I::label(&end_label));
}
BinaryOperator::LessThan => {
code.push(I::cmp(left_reg, right_reg));
code.push(I::lwi(1, result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(I::Jlt {
target: Label(end_label.clone()),
});
code.push(I::lwi(0, result_reg));
code.push(I::label(&end_label));
}
BinaryOperator::LessOrEqual => {
code.push(I::cmp(left_reg, right_reg));
code.push(I::lwi(1, result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(I::Jle {
target: Label(end_label.clone()),
});
code.push(I::lwi(0, result_reg));
code.push(I::label(&end_label));
}
BinaryOperator::GreaterThan => {
code.push(I::cmp(left_reg, right_reg));
code.push(I::lwi(1, result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(I::Jgt {
target: Label(end_label.clone()),
});
code.push(I::lwi(0, result_reg));
code.push(I::label(&end_label));
}
BinaryOperator::GreaterOrEqual => {
code.push(I::cmp(left_reg, right_reg));
code.push(I::lwi(1, result_reg));
let end_label = format!("_cmp_end_{}", self.get_unique_label());
code.push(I::Jge {
target: Label(end_label.clone()),
});
code.push(I::lwi(0, result_reg));
code.push(I::label(&end_label));
} // _ => unimplemented!(),
}
// Free operand registers (allocator will protect variables)
self.allocator.free_temp(left_reg);
self.allocator.free_temp(right_reg);
Ok((result_reg, code))
}
Expression::UnaryPostfix { op, operand, .. } => {
let (operand_reg, operand_code) =
self.generate_expression(operand, true, func_body)?;
code.append(operand_code);
let (result_reg, result_alloc) = self.allocator.alloc_temp()?;
code.append(result_alloc);
match op {
UnaryOperator::Increment => {
// postfix increment - return old value
code.push(I::mov(operand_reg, result_reg));
}
UnaryOperator::Decrement => {
// postfix decrement - return old value
code.push(I::mov(operand_reg, result_reg));
}
_ => {
return Err(CompilerError::Generic(format!(
"{op} is prefix only!"
)));
}
}
self.allocator.free_temp(operand_reg);
Ok((result_reg, code))
}
Expression::Unary { op, operand, .. } => {
let (operand_reg, operand_code) =
self.generate_expression(operand, true, func_body)?;
code.append(operand_code);
let (result_reg, result_alloc) = self.allocator.alloc_temp()?;
code.append(result_alloc);
match op {
UnaryOperator::Minus => {
// Negate: result = 0 - operand
code.push(I::sub(Register::Zero, operand_reg, result_reg));
}
UnaryOperator::Plus => {
// Just move
code.push(I::mov(operand_reg, result_reg));
}
UnaryOperator::Dereference => {
code.push(I::ldw_reg(operand_reg, result_reg));
}
UnaryOperator::AddressOf => {
// ensure the referenced variable is on the stack and return its
// address.
let (offset, alloc_code) =
self.allocator.free_register(&operand_reg)?;
code.push(alloc_code);
code.push(I::iadd_dest(
Register::Spr,
offset - self.allocator.get_stack_offset(),
result_reg,
));
}
UnaryOperator::SizeOf => {
if let Ok(id) = operand.type_id() {
let size = id.size();
code.push(I::lwi(size as u32, result_reg));
}
}
UnaryOperator::Increment => {
// prefix increment
code.push(I::mov(operand_reg, result_reg));
code.push(I::iadd_dest(operand_reg, 1, result_reg));
}
UnaryOperator::Decrement => {
// prefix decrement
code.push(I::mov(operand_reg, result_reg));
code.push(I::iadd_dest(operand_reg, -1, result_reg));
}
UnaryOperator::BitwiseNot => {
code.push(I::not(operand_reg, result_reg));
}
UnaryOperator::LogicalNot => {
return Err(CompilerError::Unimplemented(
"Assembler/ISA does not yet support logical not".to_string(),
));
}
_ => {
return Err(CompilerError::Generic(format!(
"{op} is postfix only!"
)));
}
}
self.allocator.free_temp(operand_reg);
Ok((result_reg, code))
}
Expression::Call {
func: Call { name, args },
..
} => {
// first evaluate all the args we're going to need
let mut arg_regs = Vec::new();
for arg in args.iter().rev() {
let (arg_reg, arg_code) =
self.generate_expression(arg, true, func_body)?;
code.append(arg_code);
arg_regs.push(arg_reg);
}
// Save caller-saved registers and track which ones we saved
let saved_regs = self.allocator.get_caller_saved_registers();
for reg in &saved_regs {
// spill variables to stack
code.push(self.allocator.free_register(reg).unwrap().1);
}
// Evaluate and push arguments in reverse order
for (i, arg_reg) in arg_regs.iter().enumerate() {
code.push(I::comment(format!("push arg {}", args.len() - 1 - i)));
code.push(I::push(*arg_reg));
}
if self.symbols.contains(&name.name) {
// Call local function
code.push(I::call(name.to_string()));
} else if let Some(ns) = name.namespace.clone()
&& self.imports.contains_key(&ns)
{
code.push(I::call(name.to_string()));
} else {
return Err(CompilerError::Undefined(name.clone()));
}
let result_reg: Register;
if use_result {
let (temp_result_reg, result_alloc) = self.allocator.alloc_temp()?;
result_reg = temp_result_reg;
code.append(result_alloc);
code.push(I::pop(result_reg));
// Clean up arguments
if args.len() > 1 {
for _ in 0..(args.len() - 1) {
code.push(I::pop(Register::Zero));
}
}
} else {
result_reg = Register::Zero;
// Clean up arguments
if args.len() > 0 {
for _ in 0..(args.len()) {
code.push(I::pop(Register::Zero));
}
}
}
// Free argument registers
for reg in arg_regs {
self.allocator.free_temp(reg);
}
Ok((result_reg, code))
}
Expression::IndexAccess {
expr,
index,
type_id,
} => {
let (expr_reg, expr_alloc) =
self.generate_expression(expr, true, func_body)?;
code.append(expr_alloc);
let (index_reg, index_alloc) =
self.generate_expression(index, true, func_body)?;
code.append(index_alloc);
let (result_reg, result_alloc) = self.allocator.alloc_temp()?;
code.append(result_alloc);
// add the expr pointer to the index to get the final address.
code.push(I::add(expr_reg, index_reg, result_reg));
// load the value at the address.
code.push(I::ldw_reg(result_reg, result_reg));
self.allocator.free_temp(expr_reg);
self.allocator.free_temp(index_reg);
Ok((result_reg, code))
}
Expression::MemberAccess {
expr,
field_name,
type_id,
} => Err(CompilerError::Unimplemented(
"Structs are not yet implemented!".to_string(),
)),
Expression::TypeCast {
expr,
target_type,
type_id,
} => {
let (expr_reg, expr_code) =
self.generate_expression(expr, true, func_body)?;
// not sure if we actually need to do anything here.
// for now we just return the previous expression.
Ok((expr_reg, expr_code))
}
}
}
// Helper for generating unique labels
fn get_unique_label(&mut self) -> String {
// You'd implement a counter here
static COUNTER: AtomicU32 = AtomicU32::new(0);
let val = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
(val + 1).to_string()
}
}
-797
View File
@@ -1,797 +0,0 @@
use std::fmt;
use crate::backend::dsa::registers::Register;
pub struct InsBlock {
instructions: Vec<Instruction>,
}
impl InsBlock {
pub fn new() -> Self {
Self {
instructions: vec![],
}
}
pub fn insert(&mut self, index: usize, instr: Instruction) {
self.instructions.insert(index, instr);
}
pub fn push(&mut self, instr: Instruction) {
self.instructions.push(instr);
}
pub fn append(&mut self, mut other: Self) {
self.instructions.append(&mut other.instructions);
}
pub fn extend(&mut self, instrs: impl IntoIterator<Item = Instruction>) {
self.instructions.extend(instrs);
}
pub fn is_empty(&self) -> bool {
self.instructions.is_empty()
}
pub fn len(&self) -> usize {
self.instructions.len()
}
pub fn iter(&self) -> impl Iterator<Item = &Instruction> {
self.instructions.iter()
}
}
impl From<Vec<Instruction>> for InsBlock {
fn from(instructions: Vec<Instruction>) -> Self {
Self { instructions }
}
}
impl From<Instruction> for InsBlock {
fn from(instr: Instruction) -> Self {
Self {
instructions: vec![instr],
}
}
}
#[derive(Debug, Clone)]
pub enum Instruction {
// Labels and comments
Label(Label),
Comment {
text: String,
top_level: bool,
},
Newline,
// Data Directives
Db {
label: String,
data: Vec<u8>,
},
Dh {
label: String,
data: Vec<u16>,
},
Dw {
label: String,
data: Vec<u32>,
},
DString {
// alias for db.
label: String,
data: String,
},
Resx {
label: String,
size: u32,
},
// Include
Include {
name: String,
path: String,
},
// Data movement
Mov {
src: Register,
dest: Register,
},
Movs {
src: Register,
dest: Register,
},
// Memory operations
Ldb {
src: MemOperand,
dest: Register,
},
Ldh {
src: MemOperand,
dest: Register,
},
Ldw {
src: MemOperand,
dest: Register,
},
Stb {
src: Register,
dest: MemOperand,
},
Sth {
src: Register,
dest: MemOperand,
},
Stw {
src: Register,
dest: MemOperand,
},
// Immediate loads
Lli {
imm: Imm,
dest: Register,
},
Lui {
imm: Imm,
dest: Register,
},
Lwi {
imm: Imm,
dest: Register,
},
LwiLabel {
label: String,
dest: Register,
},
// Arithmetic
Add {
src1: Register,
src2: Register,
dest: Register,
},
Sub {
src1: Register,
src2: Register,
dest: Register,
},
IAdd {
src: Register,
imm: Imm,
dest: Option<Register>,
},
ISub {
src: Register,
imm: Imm,
dest: Option<Register>,
},
Inc {
reg: Register,
},
Dec {
reg: Register,
},
// Bitwise
And {
src1: Register,
src2: Register,
dest: Register,
},
Or {
src1: Register,
src2: Register,
dest: Register,
},
Xor {
src1: Register,
src2: Register,
dest: Register,
},
Not {
src: Register,
dest: Register,
},
Nand {
src1: Register,
src2: Register,
dest: Register,
},
Nor {
src1: Register,
src2: Register,
dest: Register,
},
Xnor {
src1: Register,
src2: Register,
dest: Register,
},
// Shifts
Shl {
src1: Register,
r_shamt: Register,
i_shamt: u16,
dest: Register,
},
Shr {
src1: Register,
r_shamt: Register,
i_shamt: u16,
dest: Register,
},
// Comparison
Cmp {
reg1: Register,
reg2: Register,
},
// Jumps
Jmp {
target: Label,
},
Jeq {
target: Label,
},
Jne {
target: Label,
},
Jgt {
target: Label,
},
Jge {
target: Label,
},
Jlt {
target: Label,
},
Jle {
target: Label,
},
// Stack
Push {
reg: Register,
},
Pop {
reg: Register,
},
// Function calls
Call {
target: String,
}, // namespace::function
Return,
// System
Hlt,
Nop,
Int {
code: u8,
},
}
pub enum DataDirective {
U8(Vec<u8>),
U16(Vec<u16>),
U32(Vec<u32>),
String(String),
Char(char),
}
impl fmt::Display for Instruction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Label(l) => write!(f, "{}:", l),
Self::Newline => write!(f, ""), /* empty string as newlines are inserted */
// automatically.
Self::Comment { text, top_level } => write!(
f,
"{}",
text.lines()
.map(|line| format!(
"{}// {}",
if *top_level { "" } else { " " },
line.trim(),
))
.collect::<Vec<String>>()
.join("\n")
),
Self::Include { name, path } => write!(f, "include {name}: \"{}\"", path),
Self::Db { label, data } => write!(
f,
"db {}: {}",
label,
data.iter()
.map(|&b| format!("{:#04X}", b))
.collect::<Vec<String>>()
.join(", ")
),
Self::Dh { label, data } => write!(
f,
"dh {}: {}",
label,
data.iter()
.map(|&b| format!("{:#06X}", b))
.collect::<Vec<String>>()
.join(", ")
),
Self::Dw { label, data } => write!(
f,
"dw {}: {}",
label,
data.iter()
.map(|&b| format!("{:#08X}", b))
.collect::<Vec<String>>()
.join(", ")
),
Self::DString { label, data } => write!(f, "db {}: \"{}\"", label, data),
Self::Resx { label, size } => write!(f, "resx {}: {}", label, size),
Self::Mov { src, dest } => write!(f, " mov {}, {}", src, dest),
Self::Movs { src, dest } => write!(f, " movs {}, {}", src, dest),
Self::Ldb { src: addr, dest } => {
let (reg, offset) = reg_and_offset(addr);
write!(f, " ldb {}, {}, {}", reg, dest, offset)
}
Self::Ldh { src: addr, dest } => {
let (reg, offset) = reg_and_offset(addr);
write!(f, " ldh {}, {}, {}", reg, dest, offset)
}
Self::Ldw { src, dest } => {
let (reg, offset) = reg_and_offset(src);
write!(f, " ldw {}, {}, {}", reg, dest, offset)
}
// Self::Ldbs { addr, dest } => {
// write!(f, " ldbs {}, {}", format_mem_operand(addr), dest)
// }
// Self::Ldhs { addr, dest } => {
// write!(f, " ldhs {}, {}", format_mem_operand(addr), dest)
// }
// Self::Ldws { addr, dest } => {
// write!(f, " ldws {}, {}", format_mem_operand(addr), dest)
// }
Self::Stb { src, dest: addr } => {
let (reg, offset) = reg_and_offset(addr);
write!(f, " stb {}, {}, {}", src, reg, offset)
}
Self::Sth { src, dest: addr } => {
let (reg, offset) = reg_and_offset(addr);
write!(f, " sth {}, {}, {}", src, reg, offset)
}
Self::Stw { src, dest: addr } => {
let (reg, offset) = reg_and_offset(addr);
write!(f, " stw {}, {}, {}", src, reg, offset)
}
Self::Lli { imm, dest } => write!(f, " lli {}, {}", imm, dest),
Self::Lui { imm, dest } => write!(f, " lui {}, {}", imm, dest),
Self::Lwi { imm, dest } => write!(f, " lwi {}, {}", imm, dest),
Self::LwiLabel { label, dest } => write!(f, " lwi {}, {}", label, dest),
// arithmetic
Self::Add { src1, src2, dest } => {
write!(f, " add {}, {}, {}", src1, src2, dest)
}
Self::Sub { src1, src2, dest } => {
write!(f, " sub {}, {}, {}", src1, src2, dest)
}
Self::And { src1, src2, dest } => {
write!(f, " and {}, {}, {}", src1, src2, dest)
}
Self::Or { src1, src2, dest } => {
write!(f, " or {}, {}, {}", src1, src2, dest)
}
Self::Nand { src1, src2, dest } => {
write!(f, " nand {}, {}, {}", src1, src2, dest)
}
Self::Xor { src1, src2, dest } => {
write!(f, " xor {}, {}, {}", src1, src2, dest)
}
Self::Nor { src1, src2, dest } => {
write!(f, " nor {}, {}, {}", src1, src2, dest)
}
Self::Not { src, dest } => {
write!(f, " not {} {}", src, dest)
}
Self::Xnor { src1, src2, dest } => {
write!(f, " xnor {}, {}, {}", src1, src2, dest)
}
Self::IAdd { src, imm, dest } => {
if let Some(d) = dest {
write!(f, " addi {}, {}, {}", src, imm, d)
} else {
write!(f, " addi {}, {}", src, imm)
}
}
Self::ISub { src, imm, dest } => {
if let Some(d) = dest {
write!(f, " subi {}, {}, {}", src, imm, d)
} else {
write!(f, " subi {}, {}", src, imm)
}
}
// shift instructions
Self::Shl {
src1,
r_shamt,
i_shamt,
dest,
} => {
write!(f, " shl {}, {}, {}, {}", src1, r_shamt, i_shamt, dest)
}
Self::Shr {
src1,
r_shamt,
i_shamt,
dest,
} => {
write!(f, " shl {}, {}, {}, {}", src1, r_shamt, i_shamt, dest)
}
// increment instructions
Self::Inc { reg } => write!(f, " inc {}", reg),
Self::Dec { reg } => write!(f, " dec {}", reg),
Self::Cmp { reg1, reg2 } => write!(f, " cmp {}, {}", reg1, reg2),
// jump instructions
Self::Jmp { target } => write!(f, " jmp {}", target),
Self::Jeq { target } => write!(f, " jeq {}", target),
Self::Jne { target } => write!(f, " jne {}", target),
Self::Jgt { target } => write!(f, " jgt {}", target),
Self::Jge { target } => write!(f, " jge {}", target),
Self::Jlt { target } => write!(f, " jlt {}", target),
Self::Jle { target } => write!(f, " jle {}", target),
// stack pseudoinstructions
Self::Push { reg } => write!(f, " push {}", reg),
Self::Pop { reg } => write!(f, " pop {}", reg),
// call & return pseudoinstructions
Self::Call { target } => write!(f, " call {}", target),
Self::Return => write!(f, " return"),
// misc instructions
Self::Int { code } => write!(f, " int {}", code),
Self::Hlt => write!(f, " hlt"),
Self::Nop => write!(f, " nop"),
}
}
}
impl Instruction {
// data directives
pub fn db_string(label: impl Into<String>, data: impl Into<String>) -> Self {
Self::DString {
label: label.into(),
data: data.into(),
}
}
pub fn db_word(label: impl Into<String>, data: u32) -> Self {
Self::Dw {
label: label.into(),
data: vec![data],
}
}
pub fn db_bytes(label: impl Into<String>, data: &[u8]) -> Self {
Self::Db {
label: label.into(),
data: data.to_vec(),
}
}
// Movement
pub fn mov<R1, R2>(src: R1, dest: R2) -> Self
where
R1: Into<Register>,
R2: Into<Register>,
{
Self::Mov {
src: src.into(),
dest: dest.into(),
}
}
// Memory loads
pub fn ldw_reg<R>(base: R, dest: Register) -> Self
where
R: Into<Register>,
{
Self::Ldw {
src: MemOperand::RegIndirect(base.into()),
dest,
}
}
pub fn ldw_reg_offset<R>(base: R, dest: Register, offset: i32) -> Self
where
R: Into<Register>,
{
Self::Ldw {
src: MemOperand::RegOffset(base.into(), offset),
dest,
}
}
pub fn ldw_label(label: impl Into<Label>, dest: Register) -> Self {
Self::Ldw {
src: MemOperand::Label(label.into()),
dest,
}
}
// Memory stores
pub fn stw_reg<R>(src: Register, base: R) -> Self
where
R: Into<Register>,
{
Self::Stw {
src,
dest: MemOperand::RegIndirect(base.into()),
}
}
pub fn stw_reg_offset<R>(src: Register, base: R, offset: i32) -> Self
where
R: Into<Register>,
{
Self::Stw {
src,
dest: MemOperand::RegOffset(base.into(), offset),
}
}
pub fn stw_label(src: Register, label: impl Into<Label>) -> Self {
Self::Stw {
src,
dest: MemOperand::Label(label.into()),
}
}
// Arithmetic
pub fn add(src1: Register, src2: Register, dest: Register) -> Self {
Self::Add { src1, src2, dest }
}
pub fn sub(src1: Register, src2: Register, dest: Register) -> Self {
Self::Sub { src1, src2, dest }
}
pub fn and(src1: Register, src2: Register, dest: Register) -> Self {
Self::And { src1, src2, dest }
}
pub fn or(src1: Register, src2: Register, dest: Register) -> Self {
Self::Or { src1, src2, dest }
}
pub fn xor(src1: Register, src2: Register, dest: Register) -> Self {
Self::Xor { src1, src2, dest }
}
pub fn not(src: Register, dest: Register) -> Self {
Self::Not { src, dest }
}
pub fn shl(src1: Register, r_shamt: Register, i_shamt: u16, dest: Register) -> Self {
Self::Shl {
src1,
r_shamt,
i_shamt,
dest,
}
}
pub fn shr(src1: Register, r_shamt: Register, i_shamt: u16, dest: Register) -> Self {
Self::Shr {
src1,
r_shamt,
i_shamt,
dest,
}
}
pub fn iadd(src: Register, value: i64) -> Self {
let imm = Imm(value.unsigned_abs() as u32);
if value < 0 {
Self::ISub {
src,
imm,
dest: None,
}
} else {
Self::IAdd {
src,
imm,
dest: None,
}
}
}
pub fn iadd_dest(src: Register, value: i32, dest: Register) -> Self {
let imm = Imm(value.unsigned_abs());
if value < 0 {
Self::ISub {
src,
imm,
dest: Some(dest),
}
} else {
Self::IAdd {
src,
imm,
dest: Some(dest),
}
}
}
pub fn inc(reg: Register) -> Self {
Self::Inc { reg }
}
pub fn dec(reg: Register) -> Self {
Self::Dec { reg }
}
// Immediate loads
pub fn lwi(value: u32, dest: Register) -> Self {
if value > 0xFFFF {
Self::Lwi {
imm: Imm(value),
dest,
}
} else {
Self::Lli {
imm: Imm(value),
dest,
}
}
}
pub fn lwi_label(label: impl Into<String>, dest: Register) -> Self {
Self::LwiLabel {
label: label.into(),
dest,
}
}
// Control flow
pub fn label(name: impl Into<String>) -> Self {
Self::Label(Label(name.into()))
}
pub fn jmp(target: impl Into<Label>) -> Self {
Self::Jmp {
target: target.into(),
}
}
pub fn jeq(target: impl Into<Label>) -> Self {
Self::Jeq {
target: target.into(),
}
}
pub fn cmp(reg1: Register, reg2: Register) -> Self {
Self::Cmp { reg1, reg2 }
}
// Stack
pub fn push(reg: Register) -> Self {
Self::Push { reg }
}
pub fn pop(reg: Register) -> Self {
Self::Pop { reg }
}
// Functions
pub fn call(target: impl Into<String>) -> Self {
Self::Call {
target: target.into(),
}
}
pub fn int(code: u8) -> Self {
Self::Int { code }
}
pub fn ret() -> Self {
Self::Return
}
// Utilities
pub fn comment(text: impl Into<String>) -> Self {
Self::Comment {
text: text.into(),
top_level: false,
}
}
pub fn global_comment(text: impl Into<String>) -> Self {
Self::Comment {
text: text.into(),
top_level: true,
}
}
pub fn include(name: impl Into<String>, path: impl Into<String>) -> Self {
Self::Include {
name: name.into(),
path: path.into(),
}
}
}
// Convenience trait for Label conversion
impl From<String> for Label {
fn from(s: String) -> Self {
Label(s)
}
}
impl From<&str> for Label {
fn from(s: &str) -> Self {
Label(s.to_string())
}
}
fn reg_and_offset(op: &MemOperand) -> (String, i32) {
match op {
MemOperand::RegIndirect(reg) => (reg.to_string(), 0),
MemOperand::RegOffset(reg, offset) => (reg.to_string(), *offset),
MemOperand::Label(label) => (label.to_string(), 0),
MemOperand::LabelOffset(label, offset) => (label.to_string(), *offset),
}
}
/// Memory operand for loads/stores
#[derive(Debug, Clone)]
pub enum MemOperand {
/// Register indirect: [reg]
RegIndirect(Register),
/// Register with offset: [reg + offset]
RegOffset(Register, i32),
/// Label: [label]
Label(Label),
/// Label with offset: [label + offset]
LabelOffset(Label, i32),
}
/// Immediate value (16-bit or 32-bit)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Imm(pub u32);
impl fmt::Display for Imm {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
/// Label reference
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Label(pub String);
impl fmt::Display for Label {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
-12
View File
@@ -1,12 +0,0 @@
use crate::model::{CompilerError, Program};
mod codegen;
mod instruction;
mod registers;
mod scope;
mod variable;
pub fn generate_code(ast: &Program) -> Result<String, CompilerError> {
let mut codegen = codegen::CodeGenerator::new(ast.clone());
codegen.generate()
}
-560
View File
@@ -1,560 +0,0 @@
use std::{collections::HashMap, fmt};
use crate::{
backend::dsa::instruction::{InsBlock, Instruction},
model::CompilerError,
};
/// Register allocator for DSA assembly generation
/// Manages general-purpose registers (rg0-rgf) and handles stack spilling
pub struct RegisterAllocator {
/// Available general-purpose registers
/// Maps variable names to their current location (register or stack offset)
variable_locations: HashMap<String, Location>,
/// Maps registers to the variables they currently hold
register_contents: HashMap<Register, String>,
/// Current stack offset for local variables (relative to bpr)
/// Starts at -4 (going downward from base pointer)
stack_offset: i32,
/// Track which registers are currently in use
in_use: Vec<(Register, bool)>,
}
#[derive(Debug, Clone)]
pub struct Location {
register: Option<Register>,
stack: Option<i32>,
}
impl Location {
pub fn stack(offset: i32) -> Self {
Location {
register: None,
stack: Some(offset),
}
}
pub fn register(register: Register) -> Self {
Location {
register: Some(register),
stack: None,
}
}
}
impl RegisterAllocator {
pub fn new() -> Self {
// Initialize with available GP registers (rg0-rgf = 16 registers)
let in_use = vec![
Register::Rg0,
Register::Rg1,
Register::Rg2,
Register::Rg3,
Register::Rg4,
Register::Rg5,
Register::Rg6,
Register::Rg7,
Register::Rg8,
Register::Rg9,
Register::Rga,
Register::Rgb,
Register::Rgc,
Register::Rgd,
Register::Rge,
Register::Rgf,
]
.iter()
.map(|&reg| (reg, false))
.collect();
RegisterAllocator {
// available_registers: registers,
variable_locations: HashMap::new(),
register_contents: HashMap::new(),
stack_offset: -4, // Start at -4 (first local below saved bpr)
in_use,
}
}
/// Allocate a temporary register for expression evaluation
/// Returns the register name and optionally assembly code to save it
pub fn alloc_temp(&mut self) -> Result<(Register, InsBlock), CompilerError> {
// Try to find an unused register
// println!("finding! {:#?}", self.in_use);
if let Some(reg) = self.find_free_register() {
self.in_use[reg as usize].1 = true;
return Ok((reg, InsBlock::new()));
}
// All registers in use - need to spill one
// Choose the first register with a variable we can spill
// Find a register to spill
// let reg_to_spill = self
// .available_registers
// .iter()
// .find(|reg| self.register_contents.contains_key(*reg))
// .cloned();
// if let Some(reg) = reg_to_spill {
// // Spill this variable to stack
// let spill_code = self.spill_register(&reg)?;
// code.extend(spill_code);
// self.in_use.insert(reg.clone(), true);
// return Ok((reg, code));
// }
todo!("an efficient stack spilling algorithm. needs scope awareness.");
Err(CompilerError::Generic(
"All registers are used up yet there are no variables to spill to the stack"
.to_string(),
))
}
// fn set_in_use(&mut self, reg: Register, in_use: bool) {
// self.in_use[reg as usize].1 = in_use;
// }
/// Free a temporary register after use
/// NOTE: This will NOT free registers that contain variables!
/// Variables persist throughout their scope and must not be freed
pub fn free_temp(&mut self, reg: Register) {
// Check if this register contains a variable
if self.register_contents.contains_key(&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
if !matches!(reg, Register::Zero | Register::Null) {
self.in_use[reg as usize].1 = false;
}
}
pub fn free_var(&mut self, var: &str) {
// Check if this variable is in a register
if let Some(location) = self.variable_locations.get(var).cloned() {
if let Some(reg) = location.register
&& !matches!(reg, Register::Zero | Register::Null)
{
self.register_contents.remove(&reg);
self.in_use[reg as usize].1 = false;
}
self.variable_locations.remove(var);
}
}
/// Allocate a register for a named variable
/// Returns the register and any necessary assembly code
pub fn alloc_var(
&mut self,
var_name: &str,
) -> Result<(Register, InsBlock), CompilerError> {
if let Some(mut location) = self.variable_locations.get(var_name).cloned() {
// if the var is in a register we can use it already.
if let Some(reg) = location.register {
return Ok((reg, InsBlock::new()));
}
// if the variable is on the stack only, we need to get it in a register.
if let Some(offset) = location.stack {
// Variable was pushed, need to calculate actual position and update its
// location.
let (reg, mut code) = self.alloc_temp()?;
// acknowledge var is now in a reg as well.
location.register = Some(reg);
// Load from bpr + offset (offset is negative)
// code.push(format!("\tsubi bpr {} {}", -(offset + 4), reg));
code.push(Instruction::ldw_reg_offset(
Register::Spr,
reg,
offset - self.stack_offset,
));
// Update location to register
self.variable_locations
.insert(var_name.to_string(), location);
self.register_contents.insert(reg, var_name.to_string());
return Ok((reg, code));
}
}
// Variable doesn't have a location yet, allocate a new register
let (reg, code) = self.alloc_temp()?;
self.variable_locations
.insert(var_name.to_string(), Location::register(reg));
self.register_contents.insert(reg, var_name.to_string());
Ok((reg, code))
}
/// Get the current location of a variable
pub fn _get_var_location(&self, var_name: &str) -> Option<&Location> {
self.variable_locations.get(var_name)
}
/// Load a variable into a register (allocating if necessary)
/// Returns the register and assembly code to load it
pub fn load_var(
&mut self,
var_name: &str,
) -> Result<(Register, InsBlock), CompilerError> {
self.alloc_var(var_name)
}
/// Store a value from a register into a variable
/// Updates tracking and returns any necessary assembly code
pub fn store_var(&mut self, var_name: &str, source_reg: &Register) -> InsBlock {
let mut block = InsBlock::new();
// Check if variable already has a location
if let Some(location) = self.variable_locations.get(var_name) {
// if the variable exists in a register we write to that.
match location.register {
Some(reg) if reg == *source_reg => {
block.push(Instruction::mov(*source_reg, reg));
return block;
}
_ => (),
}
// if the variable exists on the stack but not a register we write here.
if let Some(offset) = location.stack {
block.push(Instruction::stw_reg_offset(
*source_reg,
Register::Spr,
offset - self.stack_offset,
));
return block;
}
}
// Variable doesn't exist yet, we can just use the same reg.
// if we can avoid a move, absolutely do that.
// if this is true then there's no permanent variable here so it's safe to use.
if !self.register_contents.contains_key(source_reg) {
self.variable_locations
.insert(var_name.to_string(), Location::register(*source_reg));
self.register_contents
.insert(*source_reg, var_name.to_string());
self.in_use[*source_reg as usize].1 = true;
return block;
}
// if current register isn't free, (eg is another variable) we assign somewhere
// else.
if let Some(free_reg) = self.find_free_register() {
self.variable_locations
.insert(var_name.to_string(), Location::register(free_reg));
self.register_contents
.insert(free_reg, var_name.to_string());
self.in_use[free_reg as usize].1 = true;
block.push(Instruction::mov(*source_reg, free_reg));
return block;
}
// No free registers - allocate on stack
// code.push(format!("\tstw {}, bpr, {}", source_reg, self.stack_offset));
// self.variable_locations
// .insert(var_name.to_string(), Location::Stack(self.stack_offset));
// self.stack_offset -= 4; // Move to next stack slot
//
todo!("an efficient stack spilling algorithm. needs scope awareness.");
}
/// spill a register to the stack (WITHOUT FREEING)
/// DO NOT USE this if it's for a pointer!!!!
pub fn _spill_register(&mut self, reg: &Register) -> Result<InsBlock, CompilerError> {
let mut code = InsBlock::new();
// check if the variable is declared.
if let Some(var_name) = self.register_contents.get(reg).cloned()
&& let Some(location) = self.variable_locations.get_mut(&var_name)
{
// check if var is on the stack
if let Some(offset) = location.stack {
code.push(Instruction::stw_reg_offset(
*reg,
Register::Spr,
offset - self.stack_offset,
));
return Ok(code);
}
// Track that we pushed one word
self.stack_offset -= 4;
// if the variable is not on the stack:
// push register to stack (spr decrements automatically)
let offset = self.stack_offset;
code.push(Instruction::push(*reg));
// Update variable location - it's now at current spr
// Note: We track offset from bpr for consistency
location.stack = Some(offset);
Ok(code)
} else {
Err(CompilerError::Generic(format!(
"Register {} does not contain a variable to spill!",
reg
)))
}
}
/// free a register by spilling it to the stack.
/// Returns assembly code to perform the spill
pub fn free_register(
&mut self,
reg: &Register,
) -> Result<(i32, Instruction), CompilerError> {
// check if the variable is declared.
if let Some(var_name) = self.register_contents.get(reg).cloned()
&& let Some(location) = self.variable_locations.get_mut(&var_name)
{
// check if var name is on the stack
if let Some(offset) = location.stack {
// store current register value in stack location
let code = Instruction::stw_reg_offset(
*reg,
Register::Spr,
offset - self.stack_offset,
);
// free the register.
location.register = None;
self.register_contents.remove(reg);
return Ok((offset, code));
}
// Track that we pushed one word
self.stack_offset -= 4;
let offset = self.stack_offset;
let code = Instruction::push(*reg);
// Update variable location
// Note: We track offset from bpr for consistency
location.stack = Some(offset);
location.register = None;
self.register_contents.remove(reg);
Ok((offset, code))
} else {
Err(CompilerError::Generic(format!(
"Register {} does not contain a variable to spill!",
reg
)))
}
}
/// Find a free register (not currently in use)
fn find_free_register(&self) -> Option<Register> {
self.in_use
.iter()
.filter(|(_, in_use)| !*in_use)
.map(|(reg, _)| *reg)
.next()
}
/// Spill all registers to stack (useful before function calls)
pub fn _spill_all(&mut self) -> InsBlock {
let mut code = InsBlock::new();
let regs_to_spill: Vec<Register> =
self.register_contents.keys().cloned().collect();
for reg in regs_to_spill {
if let Ok(spill_code) = self.free_register(&reg) {
code.push(spill_code.1);
}
}
code
}
/// Get the total stack offset
pub fn get_stack_offset(&self) -> i32 {
self.stack_offset
}
/// Get the total stack space needed for local variables
pub fn _get_stack_size(&self) -> i32 {
-self.stack_offset // Convert negative offset to positive size
}
/// Reset allocator for a new function
pub fn reset(&mut self) {
self.variable_locations.clear();
self.register_contents.clear();
self.stack_offset = -4;
self.in_use = vec![
Register::Rg0,
Register::Rg1,
Register::Rg2,
Register::Rg3,
Register::Rg4,
Register::Rg5,
Register::Rg6,
Register::Rg7,
Register::Rg8,
Register::Rg9,
Register::Rga,
Register::Rgb,
Register::Rgc,
Register::Rgd,
Register::Rge,
Register::Rgf,
]
.iter()
.map(|&reg| (reg, false))
.collect();
}
/// Get list of registers that contain variables and are in use
/// These need to be saved before function calls
pub fn get_caller_saved_registers(&self) -> Vec<Register> {
self.register_contents
.iter()
.filter(|(reg, _)| {
self.in_use
.get(**reg as usize)
.unwrap_or(&(Register::Null, false))
.1
})
.map(|(reg, _)| *reg)
.collect()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Register {
// general purpose
Rg0 = 0,
Rg1 = 1,
Rg2 = 2,
Rg3 = 3,
Rg4 = 4,
Rg5 = 5,
Rg6 = 6,
Rg7 = 7,
Rg8 = 8,
Rg9 = 9,
Rga = 10,
Rgb = 11,
Rgc = 12,
Rgd = 13,
Rge = 14,
Rgf = 15,
// special
Bpr,
Spr,
Ret,
Acc,
// read only
Pcx,
Zero,
// null
Null,
}
impl Register {
pub fn get_gp() -> [Register; 16] {
[
Register::Rg0,
Register::Rg1,
Register::Rg2,
Register::Rg3,
Register::Rg4,
Register::Rg5,
Register::Rg6,
Register::Rg7,
Register::Rg8,
Register::Rg9,
Register::Rga,
Register::Rgb,
Register::Rgc,
Register::Rgd,
Register::Rge,
Register::Rgf,
]
}
pub fn is_gp(&self) -> bool {
(*self as u8) < 16
}
pub fn from_index(idx: usize) -> Register {
match idx {
0 => Register::Rg0,
1 => Register::Rg1,
2 => Register::Rg2,
3 => Register::Rg3,
4 => Register::Rg4,
5 => Register::Rg5,
6 => Register::Rg6,
7 => Register::Rg7,
8 => Register::Rg8,
9 => Register::Rg9,
10 => Register::Rga,
11 => Register::Rgb,
12 => Register::Rgc,
13 => Register::Rgd,
14 => Register::Rge,
15 => Register::Rgf,
_ => unreachable!("this function shouldn't ever be called with idx>15"),
}
}
}
impl fmt::Display for Register {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Rg0 => write!(f, "rg0"),
Self::Rg1 => write!(f, "rg1"),
Self::Rg2 => write!(f, "rg2"),
Self::Rg3 => write!(f, "rg3"),
Self::Rg4 => write!(f, "rg4"),
Self::Rg5 => write!(f, "rg5"),
Self::Rg6 => write!(f, "rg6"),
Self::Rg7 => write!(f, "rg7"),
Self::Rg8 => write!(f, "rg8"),
Self::Rg9 => write!(f, "rg9"),
Self::Rga => write!(f, "rga"),
Self::Rgb => write!(f, "rgb"),
Self::Rgc => write!(f, "rgc"),
Self::Rgd => write!(f, "rgd"),
Self::Rge => write!(f, "rge"),
Self::Rgf => write!(f, "rgf"),
Self::Acc => write!(f, "acc"),
Self::Ret => write!(f, "ret"),
Self::Bpr => write!(f, "bpr"),
Self::Spr => write!(f, "spr"),
Self::Zero => write!(f, "zero"),
Self::Pcx => write!(f, "pcx"),
Self::Null => write!(f, "null"),
}
}
}
-287
View File
@@ -1,287 +0,0 @@
use std::{cell::RefCell, collections::HashMap, ops::Deref, rc::Rc};
use uuid::Uuid;
use crate::{
backend::dsa::{
instruction::{InsBlock, Instruction},
registers::{Register, RegisterAllocator},
variable::Variable,
},
model::CompilerError,
};
pub struct Allocator {
stack_offset: i32,
in_use: [(Register, bool); 16],
}
pub struct TempReg(Register);
pub struct AssignedReg(Register);
pub struct StackSlot(i32);
impl Deref for TempReg {
type Target = Register;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Deref for AssignedReg {
type Target = Register;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Deref for StackSlot {
type Target = i32;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Allocator {
pub fn new() -> Self {
let mut in_use = [(Register::Null, false); 16];
in_use.copy_from_slice(&Register::get_gp().map(|r| (r, false))[0..16]);
Self {
stack_offset: 0,
in_use,
}
}
pub fn get_stack_offset(&self) -> i32 {
self.stack_offset
}
pub fn destroy_scope(&mut self, scope: &mut Scope) {
self.stack_offset = scope.entry_stack_offset;
for var in scope.variables.drain() {
if let Some(assigned) = var.1.register {
self.free_assigned(&assigned);
}
}
}
// what we need:
// - create var in register from temporary register. free temp and use it.
//
// - create var on stack from struct/array literal. return stack offset to write to.
//
// - spill var from register to stack. return stack offset to write to.
//
// - read/write var from stack+offset into register to use while preserving the stack
// slot.
//
// - read / write bytes from the stack+offset in a larger variable into a register.
pub fn read_var(&mut self, var: &mut Variable) -> Result<InsBlock, CompilerError> {
if let Some(slot) = &mut var.stack_slot {
if var.register.is_none() {
var.register = Some(self.allocate_var()?);
}
if let Some(reg) = &var.register {
return Ok(InsBlock::from(Instruction::ldw_reg_offset(
**reg,
Register::Spr,
**slot - self.stack_offset,
)));
}
unreachable!()
}
Err(CompilerError::Generic(format!(
"Tried to write var {} to stack but var was not assigned a reg and/or stack slot",
var.name
)))
}
pub fn write_var(&mut self, var: &mut Variable) -> Result<InsBlock, CompilerError> {
if let Some(slot) = &var.stack_slot {
if let Some(reg) = &var.register {
return Ok(InsBlock::from(Instruction::stw_reg_offset(
**reg,
Register::Spr,
**slot - self.stack_offset,
)));
}
}
Err(CompilerError::Generic(format!(
"Tried to write var {} to stack but var was not assigned a reg and/or stack slot",
var.name
)))
}
pub fn spill_var(&mut self, var: &mut Variable) -> Result<InsBlock, CompilerError> {
if let Some(slot) = &var.stack_slot {
let block = self.write_var(var)?;
if let Some(reg) = &var.register {
self.free_assigned(reg);
var.register = None;
}
return Ok(block);
}
// var doesn't have a stack slot so we need to create one
if let Some(reg) = &var.register {
let slot = self.allocate_stack_slot(var.size);
let block = InsBlock::from(Instruction::push(**reg));
self.free_assigned(reg);
var.register = None;
var.stack_slot = Some(slot);
return Ok(block);
}
return Err(CompilerError::Generic(
"spill_var called on a variable without a register".to_string(),
));
}
pub fn allocate_stack_slot(&mut self, size: usize) -> StackSlot {
self.stack_offset -= size as i32;
let offset = self.stack_offset;
StackSlot(offset)
}
pub fn allocate_var(&mut self) -> Result<AssignedReg, CompilerError> {
if let Some(reg) = self.find_free_register() {
Ok(AssignedReg(reg))
} else {
Err(CompilerError::Generic(
"No free registers available".to_string(),
))
}
}
pub fn allocate_temp(&mut self) -> Result<TempReg, CompilerError> {
// allocates a temporary register
if let Some(reg) = self.find_free_register() {
Ok(TempReg(reg))
} else {
todo!("an efficient stack spilling algorithm. needs scope awareness.");
}
}
pub fn free_temp(&mut self, temp: &TempReg) {
// frees a temporary register.
self.in_use[**temp as usize].1 = false;
}
fn free_assigned(&mut self, reg: &AssignedReg) {
// frees a register.
self.in_use[**reg as usize].1 = false;
}
// if we have register(s) free, return the first one.
fn find_free_register(&mut self) -> Option<Register> {
self.in_use.iter_mut().find_map(|(reg, used)| {
if !*used {
*used = true;
Some(*reg)
} else {
None
}
})
}
}
pub struct FunctionContext {
name: String,
allocator: RefCell<Allocator>,
}
impl FunctionContext {
pub fn new(name: String) -> Self {
Self {
name,
allocator: RefCell::new(Allocator::new()),
}
}
pub fn get_stack_offset(&self) -> i32 {
self.allocator.borrow().get_stack_offset()
}
}
/// scope object
pub struct Scope<'a> {
/// outer scope, for a function this will be the global scope.
parent: Option<&'a mut Scope<'a>>,
context: Rc<FunctionContext>,
/// is the scope a function body or just a loop?
/// depending on the type, ending a scope will have different behaviour
r#type: ScopeType,
/// variables
variables: HashMap<Uuid, Variable>,
entry_stack_offset: i32,
}
impl<'a> Scope<'a> {
pub fn new(parent: &'a mut Scope<'a>, r#type: ScopeType) -> Scope<'a> {
Self {
entry_stack_offset: parent.context.get_stack_offset(),
context: Rc::clone(&parent.context),
parent: Some(parent),
r#type,
variables: HashMap::new(),
}
}
pub fn close(&mut self) -> Result<(), CompilerError> {
// closing a scope means we need to drop all variables in scope and free
// registers.
for (name, var) in self.variables.iter() {
todo!()
// if let Some(reg) = var.allocated_register {}
// if let Some(offset) = var.bpr_offset {
// self.stack_offset -= offset;
// }
}
Ok(())
}
pub fn alloc_temp_reg(&mut self) -> Result<(Register, InsBlock), CompilerError> {
todo!()
}
pub fn alloc_var_reg(&mut self) -> Result<(Register, InsBlock), CompilerError> {
todo!()
}
pub fn alloc_var_stack(&mut self) -> Result<(Register, InsBlock), CompilerError> {
todo!()
}
pub fn free_var_stack(&mut self) -> Result<(Register, InsBlock), CompilerError> {
todo!()
}
pub fn free_temp_reg(&mut self) -> Result<(Register, InsBlock), CompilerError> {
todo!()
}
}
#[derive(PartialEq, Copy, Clone, Debug)]
pub enum ScopeType {
Function,
IfBlock,
LoopBlock,
}
-93
View File
@@ -1,93 +0,0 @@
use std::{collections::HashMap, hash::Hash, rc::Rc};
use uuid::Uuid;
use crate::{
backend::dsa::{
instruction::InsBlock,
registers::Register,
scope::{AssignedReg, FunctionContext, Scope, StackSlot},
},
model::{CompilerError, TypeId},
};
pub struct Variable {
pub name: String,
pub uuid: Uuid,
/// the type of the variable.
r#type: TypeId,
/// size taken up in bytes.
/// if size > 4, value must be stored on the stack.
pub size: usize,
pub stack_slot: Option<StackSlot>,
pub register: Option<AssignedReg>,
}
impl Variable {
pub fn new_uninit(name: String, r#type: TypeId) -> Self {
Self {
name,
uuid: Uuid::new_v4(),
size: r#type.size(),
r#type,
stack_slot: None,
register: None,
}
}
pub fn new(
name: String,
r#type: TypeId,
scope: &'_ mut Scope,
) -> Result<Self, CompilerError> {
let mut var = Self::new_uninit(name, r#type);
var.alloc_default(scope);
Ok(var)
}
fn alloc_default(&mut self, scope: &'_ mut Scope) {
if self.size > 4 {
self.alloc_stack(scope).unwrap();
} else {
self.alloc_register(scope).unwrap();
}
}
fn alloc_register(
&mut self,
scope: &'_ mut Scope,
) -> Result<Register, CompilerError> {
if self.size > 4 {
return Err(CompilerError::Generic(format!(
"Type {} cannot be allocated a register as it has a size of {} bytes",
self.r#type, self.size
)));
}
todo!("integrate with register alloc logic")
// self.allocated_register = Some(...)
}
fn alloc_stack(&mut self, scope: &'_ mut Scope) -> Result<usize, CompilerError> {
todo!("integrate with stack alloc logic")
// self.bpr_offset = Some(...)
}
pub fn load(&mut self, scope: &'_ mut Scope) -> Result<Register, CompilerError> {
todo!("load var from stack to reg (if possible)")
}
pub fn drop(&mut self, scope: &'_ mut Scope) -> Result<(), CompilerError> {
Ok(())
}
pub fn spill(&mut self, scope: &'_ mut Scope) -> Result<(), CompilerError> {
todo!()
}
}
-13
View File
@@ -1,13 +0,0 @@
use crate::model::{CompilerError, Program};
mod dsa;
pub fn compiler_backend(ext: &str, ast: &Program) -> Result<String, CompilerError> {
match ext {
"dsa" => Ok(dsa::generate_code(ast)?),
_ => Err(CompilerError::Generic(format!(
"File type {} not supported",
ext
))),
}
}
-336
View File
@@ -1,336 +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,
}
#[allow(unused)]
pub enum Type {
Int32,
Int16,
Int8,
Uint32,
Uint16,
Uint8,
Char,
}
#[derive(Debug, Clone)]
pub struct Token {
pub token_type: TokenType,
pub line: usize,
pub col: usize,
}
impl Token {
pub fn new(token_type: TokenType, line: usize, col: usize) -> Self {
Self {
token_type,
line,
col,
}
}
}
// ============================================================================
// Lexer
// ============================================================================
pub struct Lexer {
source: Vec<char>,
pos: usize,
line: usize,
col: usize,
}
impl Lexer {
pub fn new(source: &str) -> Self {
Self {
source: source.chars().collect(),
pos: 0,
line: 1,
col: 1,
}
}
fn error(&self, msg: &str) -> String {
format!(
"Lexer error at line {}, col {}: {}",
self.line, self.col, msg
)
}
fn peek(&self, offset: usize) -> Option<char> {
self.source.get(self.pos + offset).copied()
}
fn advance(&mut self) -> Option<char> {
if self.pos >= self.source.len() {
return None;
}
let ch = self.source[self.pos];
self.pos += 1;
if ch == '\n' {
self.line += 1;
self.col = 1;
} else {
self.col += 1;
}
Some(ch)
}
fn skip_whitespace(&mut self) {
while let Some(ch) = self.peek(0) {
if ch.is_whitespace() {
self.advance();
} else {
break;
}
}
}
fn skip_comment(&mut self) {
if self.peek(0) == Some('/') && self.peek(1) == Some('/') {
while let Some(ch) = self.peek(0) {
if ch == '\n' {
break;
}
self.advance();
}
}
}
fn read_number(&mut self) -> i32 {
let mut num_str = String::new();
while let Some(ch) = self.peek(0) {
if ch.is_ascii_digit() {
num_str.push(ch);
self.advance();
} else {
break;
}
}
num_str.parse().unwrap_or(0)
}
fn read_identifier(&mut self) -> String {
let mut ident = String::new();
while let Some(ch) = self.peek(0) {
if ch.is_alphanumeric() || ch == '_' {
ident.push(ch);
self.advance();
} else {
break;
}
}
ident
}
fn read_string(&mut self) -> Result<String, String> {
let mut string = String::new();
self.advance(); // Consume the opening quote
while let Some(ch) = self.peek(0) {
if ch == '"' {
self.advance(); // Consume the closing quote
return Ok(string);
} else if ch == '\\' {
self.advance(); // Consume the backslash
if let Some(escaped_char) = self.peek(0) {
string.push(escaped_char);
self.advance();
}
} else {
string.push(ch);
self.advance();
}
}
Err(String::from("Unexpected EOF"))
}
fn read_char(&mut self) -> Result<char, String> {
self.advance(); // Consume the opening quote
if let Some(ch) = self.peek(0) {
self.advance();
if self.peek(0) == Some('\'') {
self.advance();
return Ok(ch);
} else {
Err(String::from("expected closing quote"))
}
} else {
Err(String::from("expected character"))
}
}
pub fn tokenize(&mut self) -> Result<Vec<Token>, String> {
let mut tokens = Vec::new();
loop {
self.skip_whitespace();
self.skip_comment();
if self.pos >= self.source.len() {
break;
}
let line = self.line;
let col = self.col;
let ch = self.peek(0).unwrap();
let token_type = if ch.is_ascii_digit() {
let num = self.read_number();
TokenType::Number(num)
} else if ch == '"' {
let string = self.read_string()?;
TokenType::String(string)
} else if ch == '\'' {
let char = self.read_char()?;
TokenType::Char(char)
} else if ch.is_alphabetic() || ch == '_' {
let ident = self.read_identifier();
match ident.as_str() {
"int" => TokenType::Int,
"if" => TokenType::If,
"else" => TokenType::Else,
"while" => TokenType::While,
"return" => TokenType::Return,
"include" => TokenType::Include,
_ => TokenType::Identifier(ident),
}
} else {
match ch {
':' if self.peek(1) == Some(':') => {
self.advance();
self.advance();
TokenType::Namespace
}
':' => {
self.advance();
TokenType::Colon
}
'=' if self.peek(1) == Some('=') => {
self.advance();
self.advance();
TokenType::Eq
}
'!' if self.peek(1) == Some('=') => {
self.advance();
self.advance();
TokenType::Ne
}
'<' if self.peek(1) == Some('=') => {
self.advance();
self.advance();
TokenType::Le
}
'>' if self.peek(1) == Some('=') => {
self.advance();
self.advance();
TokenType::Ge
}
'+' => {
self.advance();
TokenType::Plus
}
'-' => {
self.advance();
TokenType::Minus
}
'*' => {
self.advance();
TokenType::Star
}
'/' => {
self.advance();
TokenType::Slash
}
'=' => {
self.advance();
TokenType::Assign
}
'<' => {
self.advance();
TokenType::Lt
}
'>' => {
self.advance();
TokenType::Gt
}
'(' => {
self.advance();
TokenType::LParen
}
')' => {
self.advance();
TokenType::RParen
}
'{' => {
self.advance();
TokenType::LBrace
}
'}' => {
self.advance();
TokenType::RBrace
}
';' => {
self.advance();
TokenType::Semicolon
}
',' => {
self.advance();
TokenType::Comma
}
_ => return Err(self.error(&format!("Unexpected character: {}", ch))),
}
};
tokens.push(Token::new(token_type, line, col));
}
tokens.push(Token::new(TokenType::Eof, self.line, self.col));
Ok(tokens)
}
}
-25
View File
@@ -1,25 +0,0 @@
use common::logging::log;
use crate::model::{CompilerError, Program};
use parser::Parser;
pub mod lexer;
pub mod parser;
pub fn generate_ast(input: &str) -> Result<Program, CompilerError> {
log("Tokenising Input...");
let mut lexer = lexer::Lexer::new(&input);
let tokens = lexer.tokenize().map_err(|e| CompilerError::Generic(e))?;
// println!("{tokens:?}");
log(&format!("Parsing {} Tokens...", tokens.len()));
let mut parser = Parser::new(tokens);
let ast = match parser.parse() {
Ok(ast) => ast,
Err(e) => return Err(CompilerError::Generic(e)),
};
Ok(ast)
}
-482
View File
@@ -1,482 +0,0 @@
// ============================================================================
// AST Node Types
// ============================================================================
use crate::model::{
BinaryOperator, Block, ConstExpr, Declaration, Dependency, Expression, Name, Program,
Statement, TypeId, UnaryOperator, Variable,
};
use super::lexer::{Token, TokenType};
// ============================================================================
// Parser
// ============================================================================
pub struct Parser {
tokens: Vec<Token>,
pos: usize,
}
impl Parser {
pub fn new(tokens: Vec<Token>) -> Self {
Self { tokens, pos: 0 }
}
fn error(&self, msg: &str) -> String {
let token = self.current();
format!(
"Parser error at line {}, col {}: {}",
token.line, token.col, msg
)
}
fn current(&self) -> &Token {
self.tokens
.get(self.pos)
.unwrap_or_else(|| self.tokens.last().unwrap())
}
fn peek(&self, offset: usize) -> &Token {
self.tokens
.get(self.pos + offset)
.unwrap_or_else(|| self.tokens.last().unwrap())
}
fn advance(&mut self) -> &Token {
if self.pos < self.tokens.len() - 1 {
self.pos += 1;
}
self.current()
}
fn expect(&mut self, expected: TokenType) -> Result<Token, String> {
let token = self.current().clone();
if std::mem::discriminant(&token.token_type) != std::mem::discriminant(&expected)
{
return Err(self.error(&format!(
"Expected {:?}, got {:?}",
expected, token.token_type
)));
}
self.advance();
Ok(token)
}
pub fn parse(&mut self) -> Result<Program, String> {
let mut declarations = Vec::new();
while !matches!(self.current().token_type, TokenType::Eof) {
declarations.push(self.parse_declaration()?);
}
Ok(Program { declarations })
}
fn parse_declaration(&mut self) -> Result<Declaration, String> {
// check for an import
if let TokenType::Include = self.current().token_type {
self.advance();
let name =
if let TokenType::Identifier(id) = self.current().clone().token_type {
Some(id)
} else {
None
}
.ok_or(String::from("Expected identifier"))?;
self.advance();
self.expect(TokenType::Colon)?;
let path = if let TokenType::String(id) = self.current().clone().token_type {
Some(id)
} else {
None
}
.ok_or(String::from("Expected string literal"))?;
self.advance();
return Ok(Declaration::Dependency(Dependency { name, path }));
}
self.expect(TokenType::Int)?;
let name = match &self.current().token_type {
TokenType::Identifier(s) => s.clone(),
_ => return Err(self.error("Expected identifier")),
};
self.advance();
match &self.current().token_type {
TokenType::LParen => {
// Function declaration
self.advance();
let mut params = Vec::<Variable>::new();
if !matches!(self.current().token_type, TokenType::RParen) {
self.expect(TokenType::Int)?;
match &self.current().token_type {
TokenType::Identifier(s) => {
params.push(Variable {
name: s.clone(),
type_id: TypeId::U32,
});
self.advance();
}
_ => return Err(self.error("Expected parameter name")),
}
while matches!(self.current().token_type, TokenType::Comma) {
self.advance();
self.expect(TokenType::Int)?;
match &self.current().token_type {
TokenType::Identifier(s) => {
params.push(Variable {
name: s.clone(),
type_id: TypeId::U32,
});
self.advance();
}
_ => return Err(self.error("Expected parameter name")),
}
}
}
self.expect(TokenType::RParen)?;
let body = self.parse_block()?;
Ok(Declaration::Function {
name,
params,
body,
return_type: TypeId::U32,
})
}
_ => {
// Variable declaration
let init = if matches!(self.current().token_type, TokenType::Assign) {
self.advance();
if let TokenType::Number(n) = self.current().token_type {
self.advance();
Some(ConstExpr::Number(n))
} else {
return Err(self
.error("Expected constant in global variable declaration"));
}
} else {
None
};
self.expect(TokenType::Semicolon)?;
Ok(Declaration::Variable {
var: Variable {
name,
type_id: TypeId::U32,
},
init,
is_const: false,
})
}
}
}
fn parse_block(&mut self) -> Result<Block, String> {
self.expect(TokenType::LBrace)?;
let mut statements = Vec::new();
while !matches!(self.current().token_type, TokenType::RBrace) {
statements.push(self.parse_statement()?);
}
self.expect(TokenType::RBrace)?;
Ok(statements)
}
fn parse_statement(&mut self) -> Result<Statement, String> {
match &self.current().token_type {
TokenType::LBrace => Ok(Statement::Block(self.parse_block()?)),
TokenType::If => self.parse_if_stmt(),
TokenType::While => self.parse_while_stmt(),
TokenType::Return => self.parse_return_stmt(),
TokenType::Identifier(name) => {
let name = name.clone();
// peek ahead for open paren (func call expr)
if matches!(self.peek(1).token_type, TokenType::LParen) {
let expr = self.parse_expression()?; // a function call expr
self.expect(TokenType::Semicolon)?;
return Ok(Statement::Expression { expr });
}
self.advance(); // advance past identifier
// assignment expression
if matches!(self.current().token_type, TokenType::Assign) {
self.advance();
let expr = self.parse_expression()?;
self.expect(TokenType::Semicolon)?;
Ok(Statement::Assign {
varname: name,
value: expr,
})
}
// var expression
else {
self.expect(TokenType::Semicolon)?;
Ok(Statement::Expression {
expr: Expression::Variable {
name: Name {
name,
namespace: None,
},
expr_type: None,
},
})
}
}
TokenType::Int => {
// Local variable declaration
self.advance();
let name = match &self.current().token_type {
TokenType::Identifier(s) => s.clone(),
_ => return Err(self.error("Expected variable name")),
};
self.advance();
let init = if matches!(self.current().token_type, TokenType::Assign) {
self.advance();
Some(self.parse_expression()?)
} else {
None
};
self.expect(TokenType::Semicolon)?;
// Convert to assignment expression statement
let expr = if let Some(init_expr) = init {
Statement::Assign {
varname: name,
value: init_expr,
}
} else {
Statement::Assign {
varname: name,
value: Expression::Empty,
}
};
Ok(expr)
}
_ => {
let expr = if matches!(self.current().token_type, TokenType::Semicolon) {
Expression::Empty
} else {
self.parse_expression()?
};
self.expect(TokenType::Semicolon)?;
Ok(Statement::Expression { expr })
}
}
}
fn parse_if_stmt(&mut self) -> Result<Statement, String> {
self.expect(TokenType::If)?;
self.expect(TokenType::LParen)?;
let condition = self.parse_expression()?;
self.expect(TokenType::RParen)?;
let then_stmt = self.parse_block()?;
let else_stmt = if matches!(self.current().token_type, TokenType::Else) {
self.advance();
self.parse_block()?
} else {
Vec::new()
};
Ok(Statement::If {
condition,
then_stmt,
else_stmt,
})
}
fn parse_while_stmt(&mut self) -> Result<Statement, String> {
self.expect(TokenType::While)?;
self.expect(TokenType::LParen)?;
let condition = self.parse_expression()?;
self.expect(TokenType::RParen)?;
let body = self.parse_block()?;
Ok(Statement::While { condition, body })
}
fn parse_return_stmt(&mut self) -> Result<Statement, String> {
self.expect(TokenType::Return)?;
let expr = if matches!(self.current().token_type, TokenType::Semicolon) {
None
} else {
Some(self.parse_expression()?)
};
self.expect(TokenType::Semicolon)?;
Ok(Statement::Return(expr))
}
fn parse_expression(&mut self) -> Result<Expression, String> {
self.parse_comparison()
}
fn parse_comparison(&mut self) -> Result<Expression, String> {
let mut expr = self.parse_additive()?;
while let Some(op) = match &self.current().token_type {
TokenType::Eq => Some(BinaryOperator::Eq),
TokenType::Ne => Some(BinaryOperator::Ne),
TokenType::Lt => Some(BinaryOperator::Lt),
TokenType::Gt => Some(BinaryOperator::Gt),
TokenType::Le => Some(BinaryOperator::Le),
TokenType::Ge => Some(BinaryOperator::Ge),
_ => None,
} {
self.advance();
let right = Box::new(self.parse_additive()?);
expr = Expression::Binary {
op,
left: Box::new(expr),
right,
type_id: None,
};
}
Ok(expr)
}
fn parse_additive(&mut self) -> Result<Expression, String> {
let mut expr = self.parse_multiplicative()?;
while let Some(op) = match &self.current().token_type {
TokenType::Plus => Some(BinaryOperator::Add),
TokenType::Minus => Some(BinaryOperator::Sub),
_ => None,
} {
self.advance();
let right = Box::new(self.parse_multiplicative()?);
expr = Expression::Binary {
op,
left: Box::new(expr),
right,
type_id: None,
};
}
Ok(expr)
}
fn parse_multiplicative(&mut self) -> Result<Expression, String> {
let mut expr = self.parse_unary()?;
while let Some(op) = match &self.current().token_type {
TokenType::Star => Some(BinaryOperator::Mul),
TokenType::Slash => Some(BinaryOperator::Div),
_ => None,
} {
self.advance();
let right = Box::new(self.parse_unary()?);
expr = Expression::Binary {
op,
left: Box::new(expr),
right,
type_id: None,
};
}
Ok(expr)
}
fn parse_unary(&mut self) -> Result<Expression, String> {
let op = match &self.current().token_type {
TokenType::Plus => Some(UnaryOperator::Plus),
TokenType::Minus => Some(UnaryOperator::Minus),
_ => None,
};
if let Some(op) = op {
self.advance();
let operand = Box::new(self.parse_unary()?);
return Ok(Expression::Unary {
op,
operand,
type_id: None,
});
}
self.parse_primary()
}
fn parse_primary(&mut self) -> Result<Expression, String> {
match &self.current().token_type.clone() {
TokenType::Number(n) => {
let value = *n;
self.advance();
Ok(Expression::Number {
value: value as isize,
type_id: None,
})
}
TokenType::Identifier(name) => {
let name = name.clone();
self.advance();
if matches!(self.current().token_type, TokenType::LParen) {
// Function call
self.advance();
let mut args = Vec::new();
if !matches!(self.current().token_type, TokenType::RParen) {
args.push(self.parse_expression()?);
while matches!(self.current().token_type, TokenType::Comma) {
self.advance();
args.push(self.parse_expression()?);
}
}
self.expect(TokenType::RParen)?;
Ok(Expression::Call {
name: Name {
name,
namespace: None,
},
args,
type_id: None,
})
} else {
Ok(Expression::Variable {
name: Name {
name,
namespace: None,
},
expr_type: None,
})
}
}
TokenType::LParen => {
self.advance();
let expr = self.parse_expression()?;
self.expect(TokenType::RParen)?;
Ok(expr)
}
_ => Err(self.error(&format!(
"Unexpected token: {:?}",
self.current().token_type
))),
}
}
}
File diff suppressed because it is too large Load Diff
-38
View File
@@ -1,38 +0,0 @@
use common::logging::log;
use crate::model::{CompilerError, Program};
use parser::{ParseResult, Parser};
// use semantic_analyser::Analyser;
pub mod lexer;
pub mod parser;
// pub mod semantic_analyser;
pub fn generate_ast(input: &str) -> Result<Program, CompilerError> {
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) => return Err(e),
ParseResult::Deny => {
return Err(CompilerError::Generic("Parser used ::Deny".to_string()));
}
};
// println!("{ast:#?}");
log("Analyzing AST...");
log("Checking Type Information...");
// let mut analyser = Analyser::new();
// analyser.analyse(ast.clone()).unwrap();
log("Type Checking Complete...");
Ok(ast)
}
-987
View File
@@ -1,987 +0,0 @@
use super::lexer::Token;
use crate::model::{
AssignmentOperator, BinaryOperator, Block, Call, CompilerError, ConstExpr,
Declaration, Dependency, Expression, Number, Program, Statement, TypeId,
UnaryOperator, Variable,
};
use crate::{expect_tt, expect_value};
use std::ops::{ControlFlow, FromResidual, Try};
#[derive(Debug, Clone)]
pub enum ParseResult<T, E> {
Accept(T),
Deny,
Reject(E),
}
pub struct Parser {
tokens: Vec<Token>,
idx: usize,
}
impl Parser {
pub fn new(tokens: Vec<Token>) -> Self {
Self { tokens, idx: 0 }
}
pub fn parse(&mut self) -> ParseResult<Program, CompilerError> {
let mut declarations = Vec::new();
while let ParseResult::Accept(_) = self.peek_next() {
declarations.push(self.parse_declaration()?);
}
ParseResult::Accept(Program { declarations })
}
fn parse_declaration(&mut self) -> ParseResult<Declaration, CompilerError> {
if expect_tt!(self.peek_next()?, Fn).accepted() {
return self.parse_func();
}
if expect_tt!(self.peek_next()?, Struct).accepted() {
return self.parse_struct();
}
if expect_tt!(self.peek_next()?, Include).accepted() {
// expect include keyword
let _ = self.next();
// expect namespace identifier
let name = expect_value!(self.next()?, Identifier)?;
// expect colon
let _ = expect_tt!(self.next()?, Colon)?;
// expect string literal (module path)
let path = expect_value!(self.next()?, String)?;
// expect semicolon
let _ = expect_tt!(self.next()?, Semicolon)?;
return ParseResult::Accept(Declaration::Dependency(Dependency {
name: name.name,
path,
}));
}
if expect_tt!(self.peek_next()?, Const, Static).accepted() {
let is_const = match self.next()? {
Token::Const => true,
Token::Static => false,
_ => {
return ParseResult::Reject(CompilerError::Generic(String::from(
"This can't happen!",
)));
}
};
let var = self.parse_var_decl()?;
let _ = expect_tt!(self.next()?, Assign)?;
let value = self.next()?;
let init = match value {
Token::String(x) => Some(ConstExpr::String(x)),
Token::SignedInt(x, _) => Some(ConstExpr::Number(x)),
Token::UnsignedInt(x, _) => Some(ConstExpr::Number(x as i32)),
_ => {
return ParseResult::Reject(CompilerError::UnexpectedToken(
value.tt().to_string(),
));
}
};
let _ = expect_tt!(self.next()?, Semicolon)?;
return ParseResult::Accept(Declaration::Variable {
var,
init,
is_const,
});
}
ParseResult::Reject(CompilerError::UnexpectedEndOfInput)
}
fn parse_struct(&mut self) -> ParseResult<Declaration, CompilerError> {
let _ = expect_tt!(self.next()?, Struct)?;
let name = expect_value!(self.next()?, Identifier)?;
let _ = expect_tt!(self.next()?, LeftBrace)?;
let mut fields = Vec::new();
while expect_tt!(self.peek_next()?, Identifier).accepted() {
let arg = self.parse_var_decl()?;
fields.push(arg);
if expect_tt!(self.peek_next()?, Comma).accepted() {
self.next()?;
} else {
break;
}
}
let _ = expect_tt!(self.next()?, RightBrace)?;
ParseResult::Accept(Declaration::Struct { name, fields })
}
fn parse_func(&mut self) -> ParseResult<Declaration, CompilerError> {
// expect function keyword
let _ = expect_tt!(self.next()?, Fn);
// expect function name
let name = expect_value!(self.next()?, Identifier)?;
// expect left paren
let _ = expect_tt!(self.next()?, LeftParen)?;
let mut params = Vec::new();
while expect_tt!(self.peek_next()?, Identifier).accepted() {
let arg = self.parse_var_decl()?;
params.push(arg);
if expect_tt!(self.peek_next()?, Comma).accepted() {
self.next()?;
} else {
break;
}
}
// expect right paren
let _ = expect_tt!(self.next()?, RightParen)?;
// see if we can parse the return type!
let mut return_type = TypeId::Void;
if expect_tt!(self.peek_next()?, RightArrow).accepted() {
let _ = self.next();
return_type = self.parse_type()?;
}
// expect vald block
if expect_tt!(self.peek_next()?, LeftBrace).accepted() {
ParseResult::Accept(Declaration::Function {
name: name.name,
params,
return_type,
body: self.parse_block()?,
})
} else {
ParseResult::Reject(CompilerError::UnexpectedToken(
self.peek_next()?.tt().to_string(),
))
}
}
fn parse_block(&mut self) -> ParseResult<Block, CompilerError> {
// expect left brace
let _ = expect_tt!(self.next()?, LeftBrace)?;
let mut block = Vec::new();
while !expect_tt!(self.peek_next()?, RightBrace).accepted() {
block.push(self.parse_statement()?);
}
// expect right brace
let _ = expect_tt!(self.next()?, RightBrace)?;
ParseResult::Accept(block)
}
fn parse_statement(&mut self) -> ParseResult<Statement, CompilerError> {
// handle if statements
if expect_tt!(self.peek_next()?, If).accepted() {
self.next()?;
let condition = self.parse_expression()?;
let then_stmt = self.parse_block()?;
if !expect_tt!(self.peek_next()?, Else).accepted() {
return ParseResult::Accept(Statement::If {
condition,
then_stmt,
else_stmt: vec![],
});
}
let _ = expect_tt!(self.next()?, Else)?;
let else_stmt = self.parse_block()?;
return ParseResult::Accept(Statement::If {
condition,
then_stmt,
else_stmt,
});
}
// handle while loops
if expect_tt!(self.peek_next()?, While).accepted() {
self.next()?;
// expect valid expression
let expr = self.parse_expression()?;
// expect valid block after
let block = self.parse_block()?;
// return result
return ParseResult::Accept(Statement::While {
condition: expr,
body: block,
});
}
// handle indefinite loops
if expect_tt!(self.peek_next()?, Loop).accepted() {
self.next()?;
// parse the inner block
return ParseResult::Accept(Statement::Loop(self.parse_block()?));
}
if expect_tt!(self.peek_next()?, Return).accepted() {
self.next()?;
// handle case where nothing is returned
if expect_tt!(self.peek_next()?, Semicolon).accepted() {
return ParseResult::Accept(Statement::Return(None));
}
let expr = self.parse_expression()?;
expect_tt!(self.next()?, Semicolon)?;
return ParseResult::Accept(Statement::Return(Some(expr)));
}
if expect_tt!(self.peek_next()?, Break).accepted() {
self.next()?;
// expect semicolon
expect_tt!(self.next()?, Semicolon)?;
// return result
return ParseResult::Accept(Statement::Break);
}
if expect_tt!(self.peek_next()?, Continue).accepted() {
self.next()?;
// expect semicolon
expect_tt!(self.next()?, Semicolon)?;
// return result
return ParseResult::Accept(Statement::Continue);
}
// handle writes to pointers!
if expect_tt!(self.peek_next()?, Star).accepted() {
self.next()?;
let left = if expect_tt!(self.peek_next()?, Identifier).accepted() {
let identifier = expect_value!(self.next()?, Identifier)?;
Expression::Variable {
name: identifier,
expr_type: None,
}
} else if expect_tt!(self.peek_next()?, LeftParen).accepted() {
self.next()?;
let expr = self.parse_expression()?;
let _ = expect_tt!(self.next()?, RightParen).accepted();
expr
} else {
return ParseResult::Reject(CompilerError::UnexpectedToken(
self.peek_next()?.tt().to_string(),
));
};
let _ = expect_tt!(self.next()?, Assign)?;
let right = self.parse_expression()?;
// expect semicolon
expect_tt!(self.next()?, Semicolon)?;
// return result
return ParseResult::Accept(Statement::PtrWrite {
ptr: left,
value: right,
});
}
// handle let statements (declarations)
if expect_tt!(self.peek_next()?, Let).accepted() {
self.next();
// expect variable name and type.
let name = self.parse_var_decl()?;
// handle uninitialised variable case
if expect_tt!(self.peek_next()?, Semicolon).accepted() {
self.next();
return ParseResult::Accept(Statement::Declaration {
var: name,
value: None,
});
}
// handle initialised case
// expect equals
let _ = expect_tt!(self.next()?, Assign)?;
// expect a valid expression
let expr = self.parse_expression()?;
let _ = expect_tt!(self.next()?, Semicolon);
// return statement
return ParseResult::Accept(Statement::Declaration {
var: name,
value: Some(expr),
});
}
// handle an in-place function call
if let ParseResult::Accept(name) = expect_value!(self.peek_next()?, Identifier)
&& let ParseResult::Accept(operator) = expect_tt!(
self.peek(1)?,
Assign,
PlusEqual,
MinusEqual,
StarEqual,
SlashEqual,
PercentEqual,
AndEqual,
OrEqual,
XorEqual,
ShlEqual,
ShrEqual
)
{
// consume name token
self.next()?;
// pattern match to find operator
let operator = match operator {
Token::Assign => AssignmentOperator::Assign,
Token::PlusEqual => AssignmentOperator::AddAssign,
Token::MinusEqual => AssignmentOperator::SubAssign,
Token::StarEqual => AssignmentOperator::MulAssign,
Token::SlashEqual => AssignmentOperator::DivAssign,
Token::PercentEqual => AssignmentOperator::ModAssign,
Token::AndEqual => AssignmentOperator::AndAssign,
Token::OrEqual => AssignmentOperator::OrAssign,
Token::XorEqual => AssignmentOperator::XorAssign,
Token::ShlEqual => AssignmentOperator::LeftShiftAssign,
Token::ShrEqual => AssignmentOperator::RightShiftAssign,
_ => {
return ParseResult::Reject(CompilerError::UnexpectedToken(
self.peek_next()?.tt().to_string(),
));
}
};
// consume operator token
self.next()?;
let value = self.parse_expression()?;
let _ = expect_tt!(self.next()?, Semicolon);
return ParseResult::Accept(Statement::Assign {
varname: name.name,
operator,
value,
});
}
// parse an expression and a semicolon
let expr = self.parse_expression()?;
let _ = expect_tt!(self.next()?, Semicolon)?;
ParseResult::Accept(Statement::Expression { expr })
}
fn parse_expression(&mut self) -> ParseResult<Expression, CompilerError> {
self.parse_logical_or()
}
fn parse_logical_or(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_logical_and()?;
let op = match self.peek_next()? {
Token::LogicalOr => BinaryOperator::LogicalOr,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_logical_or()?),
type_id: Some(TypeId::U32),
})
}
fn parse_logical_and(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_bitwise_or()?;
let op = match self.peek_next()? {
Token::LogicalAnd => BinaryOperator::LogicalAnd,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_logical_and()?),
type_id: Some(TypeId::U32),
})
}
fn parse_bitwise_or(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_bitwise_xor()?;
let op = match self.peek_next()? {
Token::Pipe => BinaryOperator::BitwiseOr,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_bitwise_or()?),
type_id: Some(TypeId::U32),
})
}
fn parse_bitwise_xor(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_bitwise_and()?;
let op = match self.peek_next()? {
Token::Caret => BinaryOperator::BitwiseXor,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_bitwise_xor()?),
type_id: Some(TypeId::U32),
})
}
fn parse_bitwise_and(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_comparison()?;
let op = match self.peek_next()? {
Token::Ampersand => BinaryOperator::BitwiseAnd,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_bitwise_and()?),
type_id: Some(TypeId::U32),
})
}
fn parse_comparison(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_shift()?;
let op = match self.peek_next()? {
Token::EqualEqual => BinaryOperator::Equal,
Token::BangEqual => BinaryOperator::NotEqual,
Token::Less => BinaryOperator::LessThan,
Token::Greater => BinaryOperator::GreaterThan,
Token::LessEqual => BinaryOperator::LessOrEqual,
Token::GreaterEqual => BinaryOperator::GreaterOrEqual,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_comparison()?),
type_id: Some(TypeId::Bool),
})
}
fn parse_shift(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_additive()?;
let op = match self.peek_next()? {
Token::LeftShift => BinaryOperator::LeftShift,
Token::RightShift => BinaryOperator::RightShift,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_shift()?),
type_id: Some(TypeId::U32),
})
}
fn parse_additive(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_multiplicative()?;
let op = match self.peek_next()? {
Token::Plus => BinaryOperator::Add,
Token::Minus => BinaryOperator::Sub,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_additive()?),
type_id: Some(TypeId::U32),
})
}
fn parse_multiplicative(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_unary()?;
let op = match self.peek_next()? {
Token::Star => BinaryOperator::Mul,
Token::Slash => BinaryOperator::Div,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_multiplicative()?),
type_id: None,
})
}
fn parse_unary(&mut self) -> ParseResult<Expression, CompilerError> {
let op = match self.peek_next()? {
// prefix inc/dec
Token::PlusPlus => UnaryOperator::Increment,
Token::MinusMinus => UnaryOperator::Decrement,
// arithmetic
Token::Plus => UnaryOperator::Plus,
Token::Minus => UnaryOperator::Minus,
// pointer
Token::Star => UnaryOperator::Dereference,
Token::Ampersand => UnaryOperator::AddressOf,
// boolean
Token::Bang => UnaryOperator::LogicalNot,
Token::Tilde => UnaryOperator::BitwiseNot,
Token::SizeOf => UnaryOperator::SizeOf,
_ => {
let expr = self.parse_primary()?;
return self.parse_postfix(expr);
}
};
self.next()?;
let operand = Box::new(self.parse_unary()?);
ParseResult::Accept(Expression::Unary {
op,
operand,
type_id: None,
})
}
fn parse_postfix(
&mut self,
mut expr: Expression,
) -> ParseResult<Expression, CompilerError> {
loop {
match self.peek_next()? {
// Type cast: expr as Type
Token::As => {
self.next()?; // consume 'as'
let target_type = self.parse_type()?;
expr = Expression::TypeCast {
expr: Box::new(expr),
target_type,
type_id: None,
};
}
// Postfix increment/decrement
Token::PlusPlus => {
self.next()?;
expr = Expression::UnaryPostfix {
op: UnaryOperator::Increment,
operand: Box::new(expr),
type_id: None,
};
}
Token::MinusMinus => {
self.next()?;
expr = Expression::UnaryPostfix {
op: UnaryOperator::Decrement,
operand: Box::new(expr),
type_id: None,
};
}
// Array indexing: expr[index]
Token::LeftBracket => {
self.next()?; // consume '['
let index = Box::new(self.parse_expression()?);
let _ = expect_tt!(self.next()?, RightBracket)?;
expr = Expression::IndexAccess {
expr: Box::new(expr),
index,
type_id: None,
};
}
// Function call: expr(args...)
Token::LeftParen => {
self.next()?; // consume '('
let mut args = Vec::new();
if !matches!(self.peek_next()?, Token::RightParen) {
loop {
args.push(self.parse_expression()?);
if !matches!(self.peek_next()?, Token::Comma) {
break;
}
self.next()?; // consume comma
}
}
let _ = expect_tt!(self.next()?, RightParen)?;
if let Expression::Variable { name, .. } = expr {
expr = Expression::Call {
func: Call { name, args },
type_id: None,
};
}
}
// Member access: expr.member (if you support structs)
Token::Dot => {
self.next()?;
let field_name = expect_value!(self.next()?, Identifier)?;
expr = Expression::MemberAccess {
expr: Box::new(expr),
field_name,
type_id: None,
};
}
// No more postfix operations
_ => break,
}
}
ParseResult::Accept(expr)
}
fn parse_primary(&mut self) -> ParseResult<Expression, CompilerError> {
match self.peek_next()? {
Token::UnsignedInt(value, type_id) => {
self.next()?;
ParseResult::Accept(Expression::Number(Number::Unsigned(value, type_id)))
}
Token::SignedInt(value, type_id) => {
self.next()?;
ParseResult::Accept(Expression::Number(Number::Signed(value, type_id)))
}
Token::String(value) => {
self.next()?;
ParseResult::Accept(Expression::StringLiteral(value))
}
Token::Char(value) => {
self.next()?;
ParseResult::Accept(Expression::CharLiteral(value))
}
Token::Identifier(name) => {
self.next()?;
// if the next token isn't the beginning of a struct literal this is just
// an identifier.
if !expect_tt!(self.peek_next()?, LeftBrace).accepted() {
return ParseResult::Accept(Expression::Variable {
name,
expr_type: None,
});
}
let _ = self.next()?;
let mut fields = Vec::new();
while !expect_tt!(self.peek_next()?, RightBrace).accepted() {
let name = expect_value!(self.next()?, Identifier)?;
let _ = expect_tt!(self.next()?, Colon)?;
let expr = self.parse_expression()?;
fields.push((name, expr));
if expect_tt!(self.peek_next()?, Comma).accepted() {
self.next()?;
} else {
break;
}
}
let _ = expect_tt!(self.next()?, RightBrace)?;
ParseResult::Accept(Expression::StructLiteral {
name,
fields,
type_id: None,
})
}
Token::LeftBracket => {
self.next()?; // consume '['
let mut elements = Vec::new();
if !matches!(self.peek_next()?, Token::RightBracket) {
loop {
elements.push(self.parse_expression()?);
if !matches!(self.peek_next()?, Token::Comma) {
break;
}
self.next()?; // consume comma
}
}
expect_tt!(self.next()?, RightBracket)?;
ParseResult::Accept(Expression::ArrayLiteral {
elements,
type_id: None,
})
}
Token::LeftParen => {
self.next()?;
let expr = self.parse_expression()?;
let _ = expect_tt!(self.next()?, RightParen)?;
ParseResult::Accept(expr)
}
_ => ParseResult::Reject(CompilerError::UnexpectedToken(
self.peek_next()?.tt().to_string(),
)),
}
}
fn parse_var_decl(&mut self) -> ParseResult<Variable, CompilerError> {
let name = expect_value!(self.next()?, Identifier)?;
let _ = expect_tt!(self.next()?, Colon)?;
let type_id = self.parse_type()?;
ParseResult::Accept(Variable {
name: name.name,
type_id,
})
}
fn parse_type(&mut self) -> ParseResult<TypeId, CompilerError> {
println!("yes {:?}", self.peek_next()?);
// parse primitive or named type
if expect_tt!(self.peek_next()?, Identifier).accepted() {
return self.parse_type_identifier();
}
// parse array type
if expect_tt!(self.peek_next()?, LeftBracket).accepted() {
let _ = self.next()?;
let internal_type = self.parse_type()?;
let _ = expect_tt!(self.next()?, Semicolon)?;
let size = expect_value!(self.next()?, UnsignedInt)?;
let _ = expect_tt!(self.next()?, RightBracket)?;
return ParseResult::Accept(TypeId::Array {
r#type: Box::new(internal_type),
size: size as usize,
});
}
// parse tuple type
if expect_tt!(self.peek_next()?, LeftParen).accepted() {
let _ = self.next()?;
let mut types = Vec::new();
while !expect_tt!(self.peek_next()?, RightParen).accepted() {
types.push(self.parse_type()?);
if !expect_tt!(self.peek_next()?, Comma).accepted() {
break;
}
let _ = self.next()?;
}
let _ = expect_tt!(self.next()?, RightParen)?;
return ParseResult::Accept(TypeId::Tuple(types));
}
ParseResult::Reject(CompilerError::Generic(format!(
"Parsing type but no valid type was detected: {:?}",
self.peek_next()?
)))
}
fn parse_type_identifier(&mut self) -> ParseResult<TypeId, CompilerError> {
// get the type name incl namespace
let name = expect_value!(self.next()?, Identifier)?;
let type_id = match name.name.as_str() {
"u32" => TypeId::U32,
"u16" => TypeId::U16,
"u8" => TypeId::U8,
"i32" => TypeId::I32,
"i16" => TypeId::I16,
"i8" => TypeId::I8,
"void" => TypeId::Void,
"char" => TypeId::Char,
"str" => TypeId::Ptr(Box::new(TypeId::Char)),
_ => {
let mut generics = Vec::new();
if expect_tt!(self.peek_next()?, Less).accepted() {
let _ = self.next()?;
// loop until we find the closing '>'
while !expect_tt!(self.peek_next()?, Greater).accepted() {
generics.push(self.parse_type()?);
if !expect_tt!(self.peek_next()?, Comma).accepted() {
break;
}
let _ = self.next()?;
}
let _ = expect_tt!(self.next()?, Greater)?;
}
TypeId::UnknownCustom { name, generics }
}
};
ParseResult::Accept(type_id)
}
fn next(&mut self) -> ParseResult<Token, CompilerError> {
if self.idx >= self.tokens.len() {
ParseResult::Reject(CompilerError::UnexpectedEndOfInput)
} else {
let token = self.tokens[self.idx].clone();
self.idx += 1;
ParseResult::Accept(token)
}
}
fn peek_next(&self) -> ParseResult<Token, CompilerError> {
if self.idx >= self.tokens.len() {
ParseResult::Reject(CompilerError::UnexpectedEndOfInput)
} else {
ParseResult::Accept(self.tokens[self.idx].clone())
}
}
fn peek(&self, offset: usize) -> ParseResult<Token, CompilerError> {
if self.idx + offset >= self.tokens.len() {
ParseResult::Reject(CompilerError::UnexpectedEndOfInput)
} else {
ParseResult::Accept(self.tokens[self.idx + offset].clone())
}
}
}
impl<T, E> ParseResult<T, E> {
pub fn accepted(&self) -> bool {
matches!(self, ParseResult::Accept(_))
}
}
pub enum ParseResultResidual<T> {
Deny,
Reject(T),
}
impl<T, E> Try for ParseResult<T, E> {
type Output = T;
type Residual = ParseResultResidual<E>;
fn from_output(output: T) -> Self {
ParseResult::Accept(output)
}
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
match self {
ParseResult::Accept(v) => ControlFlow::Continue(v),
ParseResult::Deny => ControlFlow::Break(ParseResultResidual::Deny),
ParseResult::Reject(e) => ControlFlow::Break(ParseResultResidual::Reject(e)),
}
}
}
impl<T, E> FromResidual for ParseResult<T, E> {
fn from_residual(residual: ParseResultResidual<E>) -> Self {
match residual {
ParseResultResidual::Deny => ParseResult::Deny,
ParseResultResidual::Reject(e) => ParseResult::Reject(e),
}
}
}
#[macro_export]
macro_rules! expect_tt {
($token:expr, $($variant:ident),+) => {{
let token = $token.clone();
let tt = token.tt().to_string();
let mut vs = String::new();
$(
let s = stringify!($variant);
vs.push_str(s);
vs.push_str("|");
)+
match tt.as_str() {
$(
stringify!($variant) => ParseResult::Accept(token),
)+
_ => {
// let expected = format!("[{}]", vec![$(stringify!($variant)),+].join(" | "));
ParseResult::Reject(CompilerError::UnexpectedToken(tt))
}
}
}};
}
#[macro_export]
macro_rules! expect_value {
($expr:expr, $variant:ident) => {{
let tok = $expr;
match tok.clone() {
Token::$variant(first, ..) => ParseResult::Accept(first),
_ => {
ParseResult::Reject(CompilerError::UnexpectedToken(tok.tt().to_string()))
}
}
}};
}
@@ -1,226 +0,0 @@
use std::collections::HashMap;
use crate::model::{
BinaryOperator, // You'll need to add this to your imports
CompilerError,
Declaration,
Dependency,
Expression,
Program,
TypeId,
UnaryOperator,
};
pub struct Analyser {
symbol_table: HashMap<String, Declaration>,
}
const NUMERIC_TYPES: &[TypeId] = &[
TypeId::U32,
TypeId::I32,
TypeId::I16,
TypeId::U16,
TypeId::I8,
TypeId::U8,
];
impl Analyser {
pub fn new() -> Self {
Self {
symbol_table: HashMap::new(),
}
}
pub fn analyse(&mut self, ast: Program) -> Result<(), CompilerError> {
// build table of global symbols.
for dec in ast.declarations {
let name = match dec.clone() {
Declaration::Function { name, .. } => name,
Declaration::Variable { var, .. } => var.name,
Declaration::Dependency(Dependency { name, .. }) => name,
};
self.symbol_table.insert(name, dec);
}
Ok(())
}
fn match_type(
actual: TypeId,
expected: Option<TypeId>,
) -> Result<TypeId, CompilerError> {
match expected {
Some(id) => {
if id != actual {
Err(CompilerError::TypeMismatch(id, actual))
} else {
Ok(actual)
}
}
None => Ok(actual),
}
}
fn get_type(
&mut self, // Changed from &self to &mut self since we modify expr
expr: &mut Expression,
expected_type: Option<TypeId>,
) -> Result<TypeId, CompilerError> {
match expr {
// Correct IFF we're expecting a void type
Expression::Empty => Self::match_type(TypeId::Void, expected_type),
// Correct IFF we're expecting a char type
Expression::CharLiteral(_) => Self::match_type(TypeId::Char, expected_type),
// Correct IFF we're expecting a string slice type
Expression::StringLiteral(_) => {
Self::match_type(TypeId::Ptr(Box::new(TypeId::Char)), expected_type)
}
Expression::Variable { name, expr_type } => {
let actual = expr_type.clone().ok_or(CompilerError::UnknownType)?;
Self::match_type(actual, expected_type)
}
Expression::Number { value, type_id } => {
// If we already know the TypeId
if let Some(id) = type_id {
return Self::match_type(id.clone(), expected_type);
}
// If we're expecting a type id, check it's numeric.
// TODO: add checks to make sure it's valid for its size eg u8 cant be
// more than 255
if let Some(expected) = expected_type {
if NUMERIC_TYPES.contains(&expected) {
*type_id = Some(expected.clone());
return Ok(expected);
} else {
return Err(CompilerError::TypeMismatch(expected, TypeId::U32));
}
}
// Default to i32 if no type information is available
*type_id = Some(TypeId::I32);
Ok(TypeId::I32)
}
Expression::Binary {
op,
left,
right,
type_id,
} => {
// For binary operations, both operands should have compatible types
// and the result type depends on the operation
let left_type = self.get_type(left, None)?;
let right_type = self.get_type(right, Some(left_type.clone()))?;
// For numeric operations, result has the same type as operands
if NUMERIC_TYPES.contains(&left_type)
&& NUMERIC_TYPES.contains(&right_type)
{
*type_id = Some(left_type);
Self::match_type(left_type, expected_type)
} else {
Err(CompilerError::TypeMismatch(left_type, right_type))
}
}
Expression::Unary {
op,
operand,
type_id,
} => {
match op {
UnaryOperator::Plus | UnaryOperator::Minus => {
// Unary +/- require numeric operands
let inner_type = self.get_type(operand, None)?;
if NUMERIC_TYPES.contains(&inner_type) {
*type_id = Some(inner_type.clone());
Self::match_type(inner_type, expected_type)
} else {
Err(CompilerError::TypeMismatch(inner_type, TypeId::I32))
}
}
UnaryOperator::Dereference => {
// For dereference (*ptr), the operand must be a pointer
// and the result type is what the pointer points to
let inner_type = self.get_type(operand, None)?;
match inner_type {
TypeId::Ptr(inner) => {
let deref_type = *inner;
*type_id = Some(deref_type.clone());
Self::match_type(deref_type, expected_type)
}
_ => Err(CompilerError::Generic(format!(
"Cannot dereference non-pointer type: {:?}",
inner_type
))),
}
}
UnaryOperator::Reference => {
// For reference (&var), we need to determine what we're taking
// a reference to, then wrap it in a Ptr
// If expected_type is Ptr(T), then operand should have type T
let expected_inner = match expected_type.clone() {
Some(TypeId::Ptr(inner)) => Some(*inner),
_ => None,
};
let inner_type = self.get_type(operand, expected_inner)?;
let ref_type = TypeId::Ptr(Box::new(inner_type));
*type_id = Some(ref_type.clone());
Self::match_type(ref_type, expected_type)
}
}
}
Expression::Call {
name,
args,
type_id,
} => match self.symbol_table.get(&name.name) {
Some(Declaration::Function {
params,
return_type,
..
}) => {
// check that we've given the right number of arguments.
if args.len() != params.len() {
return Err(CompilerError::Generic(format!(
"Function {} expected {} arguments but received {}",
name.name,
params.len(),
args.len()
)));
}
for (arg, param) in args.iter_mut().zip(params.iter()) {
// check that the argument type matches the parameter type.
let provided_type = self.get_type(arg, Some(param.type_id))?;
if provided_type != param.type_id {
return Err(CompilerError::TypeMismatch(
param.type_id,
provided_type,
));
}
}
*type_id = Some(return_type.clone());
Self::match_type(return_type.clone(), expected_type)
}
_ => Err(CompilerError::Generic(format!(
"Function {} not found in symbol table",
name.name
))),
},
}
}
}
-15
View File
@@ -1,15 +0,0 @@
use crate::model::{CompilerError, Program};
// mod c;
mod dsc;
pub fn compiler_frontend(ext: &str, data: &str) -> Result<Program, CompilerError> {
match ext {
"dsc" => Ok(dsc::generate_ast(&data)?),
// "c" => Ok(c::generate_ast(&data)?),
_ => Err(CompilerError::Generic(format!(
"File type {} not supported",
ext
))),
}
}
-72
View File
@@ -1,72 +0,0 @@
#![feature(try_trait_v2)]
use std::path::Path;
use common::logging::log;
use crate::specialised::build_specialised;
mod backend;
mod frontend;
mod model;
mod specialised;
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");
let input_ext = input_path
.extension()
.and_then(|s| s.to_str())
.unwrap_or("");
// check if we're using a specialised compiler
if let Some(output) = build_specialised(input_ext, &input) {
let result = match output {
Ok(output) => output,
Err(err) => return Err(format!("Compilation failed: {err:?}").into()),
};
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(),
));
return Ok(());
}
// Parse the input using the frontend, providing the file extension and data.
let ast = match frontend::compiler_frontend(input_ext, &input) {
Ok(ast) => ast,
Err(err) => return Err(format!("Compilation failed: {err:?}").into()),
};
println!("Parsed AST: {:#?}", ast);
let output_ext = output_path
.extension()
.and_then(|s| s.to_str())
.unwrap_or("");
// Generate the output using the backend with the parsed result.
let result = match backend::compiler_backend(output_ext, &ast) {
Ok(result) => result,
Err(err) => return Err(format!("Compilation failed: {err:?}").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(())
}
-19
View File
@@ -1,19 +0,0 @@
use std::path::Path;
fn main() {
// read from input file: syntax "c_compiler <src.c> [output.dsa]"
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
eprintln!("Usage: c_compiler <src.dsc> [output.dsa]");
return;
}
let input_file = &args[1];
let output_file = if args.len() > 2 {
&args[2]
} else {
"output.dsa"
};
compiler::compile_file(Path::new(input_file), Path::new(output_file)).unwrap();
}
-495
View File
@@ -1,495 +0,0 @@
use core::fmt;
#[allow(unused)]
#[derive(Debug, Clone)]
pub enum CompilerError {
UnexpectedToken(String),
UnexpectedEndOfInput,
UnexpectedCharacter(char),
Undefined(Name),
InvalidSyntax(String),
Generic(String),
UnknownType,
TypeMismatch(TypeId, TypeId),
Unimplemented(String),
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Name {
pub name: String,
pub namespace: Option<String>,
}
impl Name {
pub fn new(name: impl Into<String>, namespace: Option<String>) -> Self {
Self {
name: name.into(),
namespace,
}
}
}
#[derive(Debug, Clone)]
pub struct Program {
pub declarations: Vec<Declaration>,
}
#[allow(unused)]
#[derive(Debug, Clone)]
pub enum Declaration {
Function {
name: String,
return_type: TypeId,
params: Vec<Variable>,
body: Block,
},
Variable {
var: Variable,
init: Option<ConstExpr>,
is_const: bool,
},
Dependency(Dependency),
Struct {
name: Name,
fields: Vec<Variable>,
},
}
#[derive(Debug, Clone)]
pub struct Dependency {
pub name: String,
pub path: String,
}
#[allow(unused)]
#[derive(Debug, Clone, PartialEq)]
pub enum TypeId {
U8,
U16,
U32,
I8,
I16,
I32,
Bool,
Char,
Void,
Ptr(Box<TypeId>),
Ref(Box<TypeId>),
Tuple(Vec<TypeId>),
Array {
r#type: Box<TypeId>,
size: usize,
},
UnknownCustom {
name: Name,
generics: Vec<TypeId>,
},
Struct {
name: Name,
fields: Vec<TypeId>,
generics: Vec<TypeId>,
},
}
impl TypeId {
pub fn size(&self) -> usize {
match self {
Self::U8 => 1,
Self::U16 => 2,
Self::U32 => 4,
Self::I8 => 1,
Self::I16 => 2,
Self::I32 => 4,
Self::Bool => 1,
Self::Char => 1,
Self::Void => 0,
Self::Ptr(t) => t.size(),
Self::Ref(t) => t.size(),
Self::Tuple(types) => types.iter().map(|t| t.size()).sum(),
Self::Array { r#type, size } => r#type.size() * size,
Self::UnknownCustom { .. } => 1, /* TODO: calculate type size during */
// semantic analysis
Self::Struct { fields, .. } => fields.iter().map(|t| t.size()).sum(),
}
}
}
impl fmt::Display for TypeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::U8 => write!(f, "u8"),
Self::U16 => write!(f, "u16"),
Self::U32 => write!(f, "u32"),
Self::I8 => write!(f, "i8"),
Self::I16 => write!(f, "i16"),
Self::I32 => write!(f, "i32"),
Self::Bool => write!(f, "bool"),
Self::Char => write!(f, "char"),
Self::Void => write!(f, "void"),
Self::Ptr(t) => write!(f, "*{}", t),
Self::Ref(t) => write!(f, "&{}", t),
Self::Tuple(elems) => write!(
f,
"({})",
elems
.iter()
.map(|t| t.to_string())
.collect::<Vec<String>>()
.join(", ")
),
Self::Array { r#type, size } => write!(f, "[{}; {}]", r#type, size),
Self::UnknownCustom { name, generics } => {
write!(
f,
"{}<{}>",
name,
generics
.iter()
.map(|t| t.to_string())
.collect::<Vec<String>>()
.join(", ")
)
}
Self::Struct {
name,
fields,
generics,
} => write!(
f,
"struct<{}> {} {{{}}}",
generics
.iter()
.map(|t| t.to_string())
.collect::<Vec<String>>()
.join(", "),
name,
fields
.iter()
.map(|t| t.to_string())
.collect::<Vec<String>>()
.join(", ")
),
}
}
}
pub type Block = Vec<Statement>;
#[allow(unused)]
#[derive(Debug, Clone, PartialEq)]
pub struct Variable {
pub name: String,
pub type_id: TypeId,
}
#[allow(unused)]
#[derive(Debug, Clone)]
pub enum Statement {
Block(Block),
Declaration {
var: Variable,
value: Option<Expression>,
},
Assign {
varname: String,
operator: AssignmentOperator,
value: Expression,
},
PtrWrite {
ptr: Expression,
value: Expression,
},
Expression {
expr: Expression,
},
If {
condition: Expression,
then_stmt: Block,
else_stmt: Block,
},
While {
condition: Expression,
body: Vec<Statement>,
},
Loop(Block),
Defer(Call),
Break,
Continue,
Return(Option<Expression>),
}
#[derive(Debug, Clone)]
pub enum ConstExpr {
Number(i32),
String(String),
}
impl fmt::Display for ConstExpr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ConstExpr::Number(n) => write!(f, "{}", n),
ConstExpr::String(s) => write!(f, "\"{}\"", s),
}
}
}
#[allow(unused)]
#[derive(Debug, Clone)]
pub enum Expression {
Empty,
Binary {
op: BinaryOperator,
left: Box<Expression>,
right: Box<Expression>,
// Post-Semantic Analysis
type_id: Option<TypeId>,
},
Unary {
op: UnaryOperator,
operand: Box<Expression>,
// Post-Semantic Analysis
type_id: Option<TypeId>,
},
UnaryPostfix {
op: UnaryOperator,
operand: Box<Expression>,
// Post-Semantic Analysis
type_id: Option<TypeId>,
},
Variable {
name: Name,
expr_type: Option<TypeId>,
},
TypeCast {
expr: Box<Expression>,
target_type: TypeId,
// Post-Semantic Analysis
type_id: Option<TypeId>,
},
IndexAccess {
expr: Box<Expression>,
index: Box<Expression>,
// Post-Semantic Analysis
type_id: Option<TypeId>,
},
MemberAccess {
expr: Box<Expression>,
field_name: Name,
// Post-Semantic Analysis
type_id: Option<TypeId>,
},
Call {
func: Call,
// Post-Semantic Analysis
type_id: Option<TypeId>,
},
Number(Number),
StringLiteral(String),
CharLiteral(char),
ArrayLiteral {
elements: Vec<Expression>,
type_id: Option<TypeId>,
},
StructLiteral {
name: Name,
fields: Vec<(Name, Expression)>,
type_id: Option<TypeId>,
},
}
#[derive(Debug, Clone)]
pub enum Number {
Signed(i32, Option<TypeId>),
Unsigned(u32, Option<TypeId>),
}
#[derive(Debug, Clone)]
pub struct Call {
pub name: Name,
pub args: Vec<Expression>,
}
impl Expression {
pub fn is_pure(&self) -> bool {
match self {
Expression::Number { .. } => true,
Expression::StringLiteral(_) => true,
Expression::CharLiteral(_) => true,
Expression::Call { .. } => false,
Expression::Binary { left, right, .. } => left.is_pure() && right.is_pure(),
Expression::Unary { operand, .. } => operand.is_pure(),
Expression::UnaryPostfix { operand, .. } => operand.is_pure(),
Expression::Empty => true,
Expression::Variable { .. } => true,
Expression::TypeCast { expr, .. } => expr.is_pure(),
Expression::IndexAccess { expr, index, .. } => {
expr.is_pure() && index.is_pure()
}
Expression::MemberAccess { expr, .. } => expr.is_pure(),
Expression::ArrayLiteral { elements, .. } => {
elements.iter().all(|element| element.is_pure())
}
Expression::StructLiteral { fields, .. } => {
fields.iter().all(|(_, expr)| expr.is_pure())
}
}
}
pub fn type_id(&self) -> Result<TypeId, CompilerError> {
match self {
Expression::Number(
Number::Signed(_, type_id) | Number::Unsigned(_, type_id),
) => type_id.clone().ok_or(CompilerError::UnknownType),
Expression::StringLiteral(_) => Ok(TypeId::Ptr(Box::new(TypeId::Char))),
Expression::CharLiteral(_) => Ok(TypeId::Char),
Expression::Call { type_id, .. } => {
type_id.clone().ok_or(CompilerError::UnknownType)
}
Expression::Binary { type_id, .. } => {
type_id.clone().ok_or(CompilerError::UnknownType)
}
Expression::Unary { type_id, .. } => {
type_id.clone().ok_or(CompilerError::UnknownType)
}
Expression::UnaryPostfix { type_id, .. } => {
type_id.clone().ok_or(CompilerError::UnknownType)
}
Expression::Empty => Ok(TypeId::Void),
Expression::Variable { expr_type, .. } => {
expr_type.clone().ok_or(CompilerError::UnknownType)
}
Expression::TypeCast { type_id, .. } => {
type_id.clone().ok_or(CompilerError::UnknownType)
}
Expression::IndexAccess { expr, .. } => expr.type_id(),
Expression::MemberAccess { expr, .. } => expr.type_id(),
Expression::ArrayLiteral { elements, .. } => {
let element_type = elements
.first()
.map_or(TypeId::Void, |e| e.type_id().unwrap_or(TypeId::Void));
Ok(TypeId::Array {
r#type: Box::new(element_type),
size: elements.len(),
})
}
Expression::StructLiteral { name, fields, .. } => {
let fields = fields
.iter()
.map(|(_, expr)| expr.type_id())
.collect::<Result<Vec<_>, _>>()?;
Ok(TypeId::Struct {
name: name.clone(),
fields,
generics: Vec::new(),
})
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum AssignmentOperator {
Assign,
AddAssign,
SubAssign,
MulAssign,
DivAssign,
ModAssign,
AndAssign,
OrAssign,
XorAssign,
LeftShiftAssign,
RightShiftAssign,
}
#[allow(unused)]
#[derive(Debug, Clone, PartialEq)]
pub enum BinaryOperator {
// arithmetic
Add,
Sub,
Mul,
Div,
Mod,
// comparison
Equal,
NotEqual,
LessThan,
GreaterThan,
LessOrEqual,
GreaterOrEqual,
// bitwise
BitwiseAnd,
BitwiseOr,
BitwiseXor,
// logical
LogicalAnd,
LogicalOr,
// shift
LeftShift,
RightShift,
}
impl fmt::Display for BinaryOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Add => write!(f, "+"),
Self::Sub => write!(f, "-"),
Self::Mul => write!(f, "*"),
Self::Div => write!(f, "/"),
Self::Mod => write!(f, "%"),
Self::Equal => write!(f, "=="),
Self::NotEqual => write!(f, "!="),
Self::LessThan => write!(f, "<"),
Self::GreaterThan => write!(f, ">"),
Self::LessOrEqual => write!(f, "<="),
Self::GreaterOrEqual => write!(f, ">="),
Self::BitwiseAnd => write!(f, "&"),
Self::BitwiseOr => write!(f, "|"),
Self::BitwiseXor => write!(f, "^"),
Self::LogicalAnd => write!(f, "&&"),
Self::LogicalOr => write!(f, "||"),
Self::LeftShift => write!(f, "<<"),
Self::RightShift => write!(f, ">>"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum UnaryOperator {
Plus,
Minus,
AddressOf,
Dereference,
BitwiseNot,
LogicalNot,
Increment,
Decrement,
SizeOf,
}
impl fmt::Display for UnaryOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Increment => write!(f, "++"),
Self::Decrement => write!(f, "--"),
Self::Plus => write!(f, "+"),
Self::Minus => write!(f, "-"),
Self::Dereference => write!(f, "*"),
Self::AddressOf => write!(f, "&"),
Self::BitwiseNot => write!(f, "~"),
Self::LogicalNot => write!(f, "!"),
Self::SizeOf => write!(f, "sizeof"),
}
}
}
-135
View File
@@ -1,135 +0,0 @@
#[must_use]
pub fn build(src: &str) -> String {
parse(src).join("\n")
}
#[must_use]
#[expect(clippy::too_many_lines)]
pub fn parse(src: &str) -> Vec<String> {
let stack = "0x10000";
let acc = "acc";
let rga = "rga";
let bpr = "bpr";
let spr = "spr";
let mut instrs = Vec::<String>::new();
// Define symbols
let print_start = "print";
let tokens = lex(src);
let mut idstack = Vec::<u32>::new();
// set up a stack
instrs.push(format!("\tlwi {}, {}", stack, bpr));
instrs.push(format!("\tmov {}, {}", bpr, spr));
// set up the data pointer
instrs.push(format!("{}: \t lwi 0x30000, {}", "main", rga));
for (id, tok) in tokens.iter().enumerate() {
match tok {
BfToken::Inc => {
instrs.push(format!("\tinc {}", acc));
}
BfToken::Dec => {
instrs.push(format!("\tdec {}", acc));
}
BfToken::IncPtr => {
instrs.push(format!("\tstw {}, {}, 0", acc, rga));
instrs.push(format!("\taddi {}, 4, {}", rga, rga));
instrs.push(format!("\tlwd {}, {}, 0", rga, acc));
}
BfToken::DecPtr => {
instrs.push(format!("\tstw {}, {}, 0", acc, rga));
instrs.push(format!("\tsubi {}, 4, {}", rga, rga));
instrs.push(format!("\tlwd {}, {}, 0", rga, acc));
}
BfToken::Out => {
instrs.push(format!("\tpush {}", acc));
instrs.push(format!("\tcall {}", print_start));
instrs.push(format!("\tpop zero"));
}
BfToken::In => {
instrs.push(format!("\tlwd 0x40000, {}, 0", acc));
}
BfToken::Forward => {
let loop_start = format!("loop_start_{}", id);
let loop_end = format!("loop_end_{}", id);
idstack.push(id as u32);
instrs.push(format!("\tcmp {}, zero", acc));
instrs.push(format!("\tjeq {}, zero", loop_end));
instrs.push(format!("{}: \tnop", loop_start));
}
BfToken::Back => {
if let Some(start_id) = idstack.pop() {
let loop_start = format!("loop_start_{}", start_id);
let loop_end = format!("loop_end_{}", start_id);
instrs.push(format!("\tcmp {}, zero", acc));
instrs.push(format!("\tjne {}, zero", loop_start));
instrs.push(format!("{}: \tnop", loop_end));
} else {
eprintln!("Warning: Unmatched ']' at position {}", id);
}
}
}
}
instrs.push("\thlt".to_string());
insert_lib(&mut instrs);
instrs
}
fn insert_lib(instrs: &mut Vec<String>) {
let bpr = "bpr";
let spr = "spr";
let rg0 = "rg0";
let rg1 = "rg1";
let print_start = "print";
let current = "current";
instrs.push(format!("\tdw {}, 0x20000", current));
instrs.push(format!("{}: \tpush {}", print_start, bpr));
instrs.push(format!("\tmov {}, {}", spr, bpr));
instrs.push(format!("\tlwd {}, {}, 8", bpr, rg0));
instrs.push(format!("\tlwd {}, {}, 0", current, rg1));
instrs.push(format!("\tstb {}, {}, 0", rg0, rg1));
instrs.push(format!("\taddi {}, 1, {}", rg1, rg1));
instrs.push(format!("\tstw {}, {}, 0", rg1, current));
instrs.push(format!("\tmov {}, {}", bpr, spr));
instrs.push(format!("\tpop {}", bpr));
instrs.push("\treturn".to_string());
}
enum BfToken {
Inc,
Dec,
IncPtr,
DecPtr,
Out,
In,
Forward,
Back,
}
fn lex(src: &str) -> Vec<BfToken> {
src.chars()
.filter_map(|c| match c {
'+' => Some(BfToken::Inc),
'-' => Some(BfToken::Dec),
'>' => Some(BfToken::IncPtr),
'<' => Some(BfToken::DecPtr),
'.' => Some(BfToken::Out),
',' => Some(BfToken::In),
'[' => Some(BfToken::Forward),
']' => Some(BfToken::Back),
_ => None,
})
.collect()
}
fn _create_symbol(id: u32) -> String {
format!("label_{}", id)
}
-13
View File
@@ -1,13 +0,0 @@
use crate::model::CompilerError;
pub mod brainf;
pub fn build_specialised(ext: &str, data: &str) -> Option<Result<String, CompilerError>> {
match ext {
"bf" => {
let res = brainf::build(data);
Some(Ok(res))
}
_ => None,
}
}
File diff suppressed because it is too large Load Diff
-429
View File
@@ -1,429 +0,0 @@
# DSA Instruction Set Architecture Specification
## Overview
The Damn Simple Architecture (DSA) is a 32-bit RISC-style architecture designed for simplicity and educational purposes. This document provides the complete instruction set architecture specification, including all hardware instructions, registers, and encoding formats.
## Data Types and Sizes
| Type | Size | Alignment |
|------|------|-----------|
| Byte | 8 bits | 1-byte aligned |
| Halfword | 16 bits | 2-byte aligned |
| Word | 32 bits | 4-byte aligned |
**Note on Endianness:**
- Instructions and numeric data in memory: Little-endian
- Data defined via `db/dh/dw` directives: Big-endian (assembler-specific)
## Registers
DSA provides 32 programmer-accessible registers plus several internal system registers.
### Programmer-Accessible Registers
| Hex | Register | Type | Description |
|-----|----------|------|-------------|
| 0x00-0x0F | **rg0-rgf** | General Purpose | 16 general-purpose registers for variables and temporary values |
| 0x10 | **acc** | Special | Accumulator for calculations and temporary storage<br/>⚠️ Used as scratch by pseudo-instructions - volatile |
| 0x11 | **spr** | Special | Stack pointer - points to top of stack |
| 0x12 | **bpr** | Special | Base pointer - used for stack frame management |
| 0x13 | **ret** | Special | Return address register - used for function returns |
| 0x14 | **idr** | Privileged | Interrupt descriptor table address<br/>Read/write triggers protection fault in user mode |
| 0x15 | **mmr** | Privileged | Hardware memory map table address<br/>Read/write triggers protection fault in user mode |
| 0x16 | **zero** | Read-only | Constant zero value<br/>Reads always return 0, writes are discarded |
| 0x17 | **noreg** | Placeholder | Indicates unused register field<br/>Read/write triggers illegal instruction fault<br/>Can also be referenced as **null** |
| 0x18-0x1F | - | Reserved | Reserved for future use |
**System Registers (indices 0x18-0x1C):**
These exist in the encoding space but are internal to the CPU implementation:
| Hex | Register | Description |
|-----|----------|-------------|
| 0x18 | **mar** | Memory Address Register (CPU internal) |
| 0x19 | **mdr** | Memory Data Register (CPU internal) |
| 0x1A | **sts** | Status Register (CPU internal) |
| 0x1B | **cir** | Current Instruction Register (CPU internal) |
| 0x1C | **pcx** | Program Counter (read-only, special access) |
**Note on PCX (Program Counter):**
- PCX can be read in certain contexts (e.g., stored during CALL)
- Writing to PCX triggers a protection fault
- PCX is automatically updated by jump and branch instructions
### Status Register (STS) Layout
The status register is a 32-bit register with the following flag bits:
| Bit | Name | Description | Boot Value |
|-----|------|-------------|------------|
| 0 | **Equal** | Set if last comparison result was equal | 0 |
| 1 | **GreaterThan** | Set if last comparison result was greater than | 0 |
| 2 | **GreaterThanOrEqual** | Set if last comparison was greater than or equal | 0 |
| 3 | **LessThan** | Set if last comparison result was less than | 0 |
| 4 | **LessThanOrEqual** | Set if last comparison was less than or equal | 0 |
| 5 | **Zero** | Set if last arithmetic/logic operation result was zero | 0 |
| 6-31 | - | Reserved | 0 |
## Instruction Encoding Formats
DSA uses three instruction encoding formats:
### R-Type (Register) Instructions
Used for operations with register operands only, including shifts.
```
31-26 | 25-21 | 20-16 | 15-11 | 10-6 | 5-0
--------+---------+---------+---------+--------+-------
Opcode | SrcReg1 | SrcReg2 | DestReg | ShiftAmt | Unused
```
- **Opcode** (6 bits): Instruction operation code
- **SrcReg1** (5 bits): First source register
- **SrcReg2** (5 bits): Second source register
- **DestReg** (5 bits): Destination register
- **ShiftAmt** (5 bits): Shift amount (for shift instructions only, must be 0 otherwise)
- **Unused** (6 bits): Must be 0
**Important Rules:**
- ShiftAmt must be 0 for non-shift instructions (else illegal instruction fault)
- Unused register fields must be set to `noreg` (0x17) if not used
- Using registers in unexpected positions may cause illegal instruction fault
### I-Type (Immediate) Instructions
Used for operations with a 16-bit immediate value.
```
31-26 | 25-21 | 20-16 | 15-0
--------+---------+---------+-------------
Opcode | SrcReg | DestReg | 16-bit Immediate
```
- **Opcode** (6 bits): Instruction operation code
- **SrcReg** (5 bits): Source register (base for memory ops)
- **DestReg** (5 bits): Destination register (or offset register for jumps)
- **Immediate** (16 bits): Signed 16-bit immediate value or offset
**Usage:**
- Arithmetic: Immediate is a signed value
- Memory access: Immediate is a signed byte offset from base address
- Branches: Immediate is a signed offset added to base register
- Literal loads: Immediate is unsigned 16-bit value
### J-Type (Jump) Instructions
Used for absolute jumps with large address ranges.
```
31-26 | 25-0
--------+----------------------
Opcode | 26-bit Address
```
- **Opcode** (6 bits): Jump instruction code
- **Address** (26 bits): Partial address for jump
**Address Calculation:**
1. Left-shift the 26-bit address by 2 (word alignment)
2. OR with upper 4 bits of current PCX
3. Result is final 32-bit jump address
**Jump Range:** 256MB region around current PC (±128MB)
**Note:** J-type instructions are defined but currently unused. Use I-type JMP with register addressing for all jumps.
## Hardware Instructions
### Data Movement
| Hex | Mnemonic | Type | Operands | Description |
|-----|----------|------|----------|-------------|
| 0x00 | **NOP** | R | - | No operation - does nothing |
| 0x01 | **MOV** | R | SrcReg, DestReg | Copy value from SrcReg to DestReg |
| 0x02 | **MOVS** | R | SrcReg, DestReg | Copy with sign extension to fill 32 bits |
**MOV/MOVS Details:**
- MOV performs direct copy (all 32 bits)
- MOVS sign-extends the value (useful after byte/halfword loads)
- Both instructions set the Zero flag if result is zero
### Memory Access - Load Instructions
All loads require proper alignment or trigger an alignment fault.
| Hex | Mnemonic | Type | Operands | Description |
|-----|----------|------|----------|-------------|
| 0x03 | **LDB** | I | BaseReg, DestReg, Offset | Load byte (8-bit), zero-extend to 32 bits |
| 0x04 | **LDBS** | I | BaseReg, DestReg, Offset | Load byte (8-bit), sign-extend to 32 bits |
| 0x05 | **LDH** | I | BaseReg, DestReg, Offset | Load halfword (16-bit), zero-extend to 32 bits |
| 0x06 | **LDHS** | I | BaseReg, DestReg, Offset | Load halfword (16-bit), sign-extend to 32 bits |
| 0x07 | **LDW** | I | BaseReg, DestReg, Offset | Load word (32-bit) |
**Load Operation:**
- Effective address = BaseReg + SignExtend(Offset)
- Offset is a signed 16-bit value
- Alignment requirements:
- LDB/LDBS: No alignment required (byte-aligned)
- LDH/LDHS: Must be 2-byte aligned
- LDW: Must be 4-byte aligned
**Encoding Note:**
In machine code, the order is: BaseReg (SrcReg field), DestReg field, Offset (Immediate field)
### Memory Access - Store Instructions
All stores require proper alignment or trigger an alignment fault.
| Hex | Mnemonic | Type | Operands | Description |
|-----|----------|------|----------|-------------|
| 0x08 | **STB** | I | SrcReg, BaseReg, Offset | Store byte (8-bit) to memory |
| 0x09 | **STH** | I | SrcReg, BaseReg, Offset | Store halfword (16-bit) to memory |
| 0x0A | **STW** | I | SrcReg, BaseReg, Offset | Store word (32-bit) to memory |
**Store Operation:**
- Effective address = BaseReg + SignExtend(Offset)
- Offset is a signed 16-bit value
- Only the relevant bits are stored (8, 16, or 32)
- Alignment requirements:
- STB: No alignment required (byte-aligned)
- STH: Must be 2-byte aligned
- STW: Must be 4-byte aligned
**Encoding Note:**
In machine code: SrcReg (SrcReg field), BaseReg (DestReg field), Offset (Immediate field)
### Immediate Load Instructions
| Hex | Mnemonic | Type | Operands | Description |
|-----|----------|------|----------|-------------|
| 0x0B | **LLI** | I | Value, DestReg | Load 16-bit value into lower 16 bits<br/>⚠️ **CLEARS upper 16 bits!** |
| 0x0C | **LUI** | I | Value, DestReg | Load 16-bit value into upper 16 bits<br/>Lower 16 bits unchanged |
**Usage for 32-bit Values:**
```
LLI 0x1234, rg0 ; rg0 = 0x00001234
LUI 0xABCD, rg0 ; rg0 = 0xABCD1234
```
**⚠️ CRITICAL:** Always execute LLI before LUI, as LLI clears the upper 16 bits!
**Note on LUI:** The assembler may shift the immediate value right by 16 bits when encoding, so specify the upper 16 bits directly (e.g., `LUI 0xABCD, rg0` not `LUI 0xABCD0000, rg0`).
**Encoding Note:**
In machine code: Value (Immediate field), DestReg (SrcReg field for LLI, SrcReg field for LUI)
### Jump and Branch Instructions
| Hex | Mnemonic | Type | Operands | Description |
|-----|----------|------|----------|-------------|
| 0x0D | **JMP** | I | Offset, BaseReg | Unconditional jump to (BaseReg + Offset) |
| 0x0E | **JEQ** | I | Offset, BaseReg | Jump if Equal flag set |
| 0x0F | **JNE** | I | Offset, BaseReg | Jump if Equal flag NOT set |
| 0x10 | **JGT** | I | Offset, BaseReg | Jump if GreaterThan flag set |
| 0x11 | **JGE** | I | Offset, BaseReg | Jump if GreaterThan OR Equal flag set |
| 0x12 | **JLT** | I | Offset, BaseReg | Jump if LessThan flag set |
| 0x13 | **JLE** | I | Offset, BaseReg | Jump if LessThan OR Equal flag set |
**Jump Calculation:**
- Target address = BaseReg + SignExtend(Offset)
- If BaseReg = zero, this becomes absolute addressing with Offset
- If BaseReg = ret, this becomes return-style addressing
- Conditional jumps check flags in STS register
**Common Patterns:**
```
JMP label, zero ; Absolute jump to label address
JMP 0, ret ; Jump to address in ret register
JMP 4, ret ; Jump to (ret + 4)
```
**Encoding Note:**
In machine code: Offset (Immediate field), BaseReg (SrcReg field) (DestReg unused, set to noreg)
### Comparison
| Hex | Mnemonic | Type | Operands | Description |
|-----|----------|------|----------|-------------|
| 0x14 | **CMP** | R | Reg1, Reg2 | Compare Reg1 with Reg2, set flags in STS |
**Flag Setting:**
- Equal: Set if Reg1 == Reg2
- GreaterThan: Set if Reg1 > Reg2 (signed)
- GreaterThanOrEqual: Set if Reg1 >= Reg2 (signed)
- LessThan: Set if Reg1 < Reg2 (signed)
- LessThanOrEqual: Set if Reg1 <= Reg2 (signed)
- Zero: Set if (Reg1 - Reg2) == 0 (same as Equal)
**Encoding Note:**
DestReg and ShiftAmt fields unused (set to noreg and 0)
### Arithmetic Instructions
| Hex | Mnemonic | Type | Operands | Description |
|-----|----------|------|----------|-------------|
| 0x15 | **INC** | R | Reg | Increment register by 1 |
| 0x16 | **DEC** | R | Reg | Decrement register by 1 |
| 0x19 | **ADD** | R | Src1, Src2, Dest | Dest = Src1 + Src2 |
| 0x1A | **SUB** | R | Src1, Src2, Dest | Dest = Src1 - Src2 |
| 0x25 | **IADD** | I | Src, Literal, Dest | Dest = Src + SignExtend(Literal) |
| 0x26 | **ISUB** | I | Src, Literal, Dest | Dest = Src - SignExtend(Literal) |
**Flag Effects:**
- Zero flag set if result is zero
- Other flags undefined after arithmetic (use CMP for comparisons)
**Encoding Notes:**
- INC/DEC: Reg in SrcReg1 field, DestReg set to noreg
- IADD/ISUB: Immediate is signed 16-bit value, all three operands required
### Bitwise Logical Operations
| Hex | Mnemonic | Type | Operands | Description |
|-----|----------|------|----------|-------------|
| 0x1B | **AND** | R | Src1, Src2, Dest | Dest = Src1 & Src2 (bitwise AND) |
| 0x1C | **OR** | R | Src1, Src2, Dest | Dest = Src1 \| Src2 (bitwise OR) |
| 0x1D | **NOT** | R | Src, Dest | Dest = ~Src (bitwise NOT) |
| 0x1E | **XOR** | R | Src1, Src2, Dest | Dest = Src1 ^ Src2 (bitwise XOR) |
| 0x1F | **NAND** | R | Src1, Src2, Dest | Dest = ~(Src1 & Src2) (bitwise NAND) |
| 0x20 | **NOR** | R | Src1, Src2, Dest | Dest = ~(Src1 \| Src2) (bitwise NOR) |
| 0x21 | **XNOR** | R | Src1, Src2, Dest | Dest = ~(Src1 ^ Src2) (bitwise XNOR) |
**Flag Effects:**
- Zero flag set if result is zero
- Other flags undefined
**Encoding Note:**
NOT uses only Src (SrcReg1) and Dest (DestReg); SrcReg2 unused (set to noreg)
### Shift Operations
| Hex | Mnemonic | Type | Operands | Description |
|-----|----------|------|----------|-------------|
| 0x17 | **SHL** | R | Reg, ShiftAmount | Shift Reg left by ShiftAmount bits<br/>Zero-fill from right |
| 0x18 | **SHR** | R | Reg, ShiftAmount | Shift Reg right by ShiftAmount bits<br/>Zero-fill from left (logical shift) |
**Shift Amount:**
- **Literal shifts**: ShiftAmount is a 5-bit literal (0-31) in assembly
- Stored in ShiftAmt field of instruction
- SrcReg2 set to noreg
- **Register shifts**: ShiftAmount is a register containing shift value
- Register specified in SrcReg2 field
- ShiftAmt field must be 0
- Only low 5 bits of register value used
**Note:** Current assembler implementation may only support literal shifts. Check assembler documentation.
**Flag Effects:**
- Zero flag set if result is zero
**Encoding Notes:**
- Reg in both SrcReg1 and DestReg fields (shifted in place)
- For literal shifts: ShiftAmt field contains shift count, SrcReg2 = noreg
- For register shifts: SrcReg2 contains register, ShiftAmt must be 0
### System and Control Instructions
| Hex | Mnemonic | Type | Operands | Description |
|-----|----------|------|----------|-------------|
| 0x22 | **INT** | I | InterruptCode | Trigger interrupt with 8-bit code<br/>Saves return address to ret register<br/>Sets bpr to kernel stack |
| 0x23 | **IRT** | R | - | Return from interrupt<br/>Restores execution context |
| 0x24 | **HLT** | R | - | Halt processor execution<br/>Stops fetch-decode-execute cycle |
**INT Behavior:**
1. Save current PCX to ret register
2. Switch bpr to kernel stack address
3. Look up interrupt handler address in interrupt descriptor table (idr)
4. Jump to handler at interrupt vector
**IRT Behavior:**
1. Restore previous execution context
2. Return to address in ret register
3. Restore user stack pointer
**Encoding Notes:**
- INT: InterruptCode in low 8 bits of Immediate field
- IRT/HLT: All register fields set to noreg, ShiftAmt to 0
### Meta Instructions (Assembler/Linker)
These instructions are used by the assembler and linker but may not represent real CPU operations.
| Hex | Mnemonic | Description |
|-----|----------|-------------|
| 0x27 | **SEGMENT** | Segment marker (implementation-specific) |
| 0x3E | **DATA** | Raw data embedding |
**Note:** The SEGMENT instruction opcode may vary between implementations (0x27 in assembler, 0x3F in some contexts). Consult your specific toolchain documentation.
## Instruction Summary Table
| Opcode | Mnemonic | Type | Category |
|--------|----------|------|----------|
| 0x00 | NOP | R | Control |
| 0x01 | MOV | R | Data Movement |
| 0x02 | MOVS | R | Data Movement |
| 0x03 | LDB | I | Memory Load |
| 0x04 | LDBS | I | Memory Load |
| 0x05 | LDH | I | Memory Load |
| 0x06 | LDHS | I | Memory Load |
| 0x07 | LDW | I | Memory Load |
| 0x08 | STB | I | Memory Store |
| 0x09 | STH | I | Memory Store |
| 0x0A | STW | I | Memory Store |
| 0x0B | LLI | I | Immediate Load |
| 0x0C | LUI | I | Immediate Load |
| 0x0D | JMP | I | Jump |
| 0x0E | JEQ | I | Branch |
| 0x0F | JNE | I | Branch |
| 0x10 | JGT | I | Branch |
| 0x11 | JGE | I | Branch |
| 0x12 | JLT | I | Branch |
| 0x13 | JLE | I | Branch |
| 0x14 | CMP | R | Comparison |
| 0x15 | INC | R | Arithmetic |
| 0x16 | DEC | R | Arithmetic |
| 0x17 | SHL | R | Shift |
| 0x18 | SHR | R | Shift |
| 0x19 | ADD | R | Arithmetic |
| 0x1A | SUB | R | Arithmetic |
| 0x1B | AND | R | Logical |
| 0x1C | OR | R | Logical |
| 0x1D | NOT | R | Logical |
| 0x1E | XOR | R | Logical |
| 0x1F | NAND | R | Logical |
| 0x20 | NOR | R | Logical |
| 0x21 | XNOR | R | Logical |
| 0x22 | INT | I | System |
| 0x23 | IRT | R | System |
| 0x24 | HLT | R | System |
| 0x25 | IADD | I | Arithmetic |
| 0x26 | ISUB | I | Arithmetic |
| 0x27 | SEGMENT | - | Meta |
| 0x3E | DATA | - | Meta |
## Exception Conditions
The following conditions trigger exceptions:
| Exception | Trigger Condition |
|-----------|------------------|
| **Illegal Instruction** | - Invalid opcode<br/>- noreg used as source/destination<br/>- ShiftAmt non-zero for non-shift instruction<br/>- Register field violations |
| **Protection Fault** | - Write to pcx register<br/>- Read/write idr or mmr in user mode<br/>- Read from noreg<br/>- Write to zero register (discarded, no fault) |
| **Alignment Fault** | - LDH/LDHS/STH with odd address<br/>- LDW/STW with address not divisible by 4 |
| **Memory Access Violation** | - Access to unmapped or protected memory<br/>- Stack overflow/underflow |
## Calling Convention
See the DSA Assembly Language Reference for the complete calling convention and ABI specification.
## Notes on Design
1. **Word Size:** All addresses and general computation is 32-bit
2. **Endianness:** Little-endian for instructions and runtime data; assembler data directives may use big-endian
3. **Stack Growth:** Stack grows **downward** (toward lower addresses) - PUSH decrements SPR
4. **Alignment:** Natural alignment required for halfword and word accesses
5. **Sign Extension:** All immediate values are sign-extended unless noted
6. **Zero Register:** Provides constant zero, writes are legal but discarded
7. **Reserved Encodings:** Opcodes 0x27-0x3D and 0x3F reserved or implementation-specific
-875
View File
@@ -1,875 +0,0 @@
# DSA Project Roadmap & Task Breakdown
> **Damn Simple Architecture** — Full ecosystem development plan including emulator, assembler, compiler, debugger, and tooling infrastructure.
---
## Table of Contents
1. [Phase 1: Foundation & Core Infrastructure](#phase-1-foundation--core-infrastructure)
- [1.1 Binary Format & Linking System](#11-binary-format--linking-system)
- [1.2 Assembler Rewrite](#12-assembler-rewrite)
- [1.3 Documentation Updates](#13-documentation-updates)
2. [Phase 2: Compiler Development](#phase-2-compiler-development)
- [2.1 Language Design & Implementation](#21-language-design--implementation)
- [2.2 Standard Library](#22-standard-library)
3. [Phase 3: Build System & Package Management](#phase-3-build-system--package-management)
- [3.1 Build System](#31-build-system)
- [3.2 Package Management System](#32-package-management-system)
4. [Phase 4: Debugger & Development Tools](#phase-4-debugger--development-tools)
- [4.1 Debug Symbol System](#41-debug-symbol-system)
- [4.2 Debugger Implementation](#42-debugger-implementation)
- [4.3 Enhanced Editor Integration](#43-enhanced-editor-integration)
5. [Phase 5: Integration & Polish](#phase-5-integration--polish)
6. [Phase 6: Future Enhancements (NTH)](#phase-6-future-enhancements-nth)
7. [Summary Timeline](#summary-timeline)
8. [Critical Path](#critical-path)
9. [Recommended Work Order](#recommended-work-order)
---
## Phase 1: Foundation & Core Infrastructure
**Estimated Duration: 34 weeks**
---
### 1.1 Binary Format & Linking System
> **Priority: CRITICAL** — Everything depends on this.
> **Total Estimate: 1.5 weeks**
---
#### 1.1.1 Design New Binary Format Specification
**Estimate: 2 days**
**Dependencies:** None
**Deliverable:** `docs/binary-format-spec.md`
- [ ] Research existing object file formats (ELF, COFF, Mach-O) for inspiration
- [ ] Design `.dsb` object file format specification
- [ ] Symbol table structure
- [ ] Relocation table format
- [ ] Section definitions (code, data, rodata, bss)
- [ ] Debug information structure
- [ ] Metadata headers
- [ ] Design `.dse` executable format specification
- [ ] Entry point definition
- [ ] Memory layout requirements
- [ ] Linking metadata
- [ ] Document format specifications in markdown
- [ ] Create format version strategy for future compatibility
---
#### 1.1.2 Implement DSB Object File Writer
**Estimate: 3 days**
**Dependencies:** 1.1.1
**Deliverable:** `dsa-binary-format` crate v0.1.0
- [ ] Create new crate: `dsa-binary-format`
- [ ] Implement object file structures
- [ ] Header structure
- [ ] Symbol table builder
- [ ] Section manager
- [ ] Relocation entry creator
- [ ] Write serialization logic
- [ ] Add validation and error handling
- [ ] Write unit tests for each structure
- [ ] Integration tests for complete object files
---
#### 1.1.3 Build Linker Program
**Estimate: 4 days**
**Dependencies:** 1.1.2
**Deliverable:** `dsa-link` executable
- [ ] Create new crate: `dsa-linker`
- [ ] Implement symbol resolution
- [ ] Global symbol table
- [ ] Symbol conflict detection
- [ ] Weak symbol handling
- [ ] Implement relocation processing
- [ ] Address calculation
- [ ] Patch generation
- [ ] Cross-section references
- [ ] Build executable generator
- [ ] Combine sections
- [ ] Generate final memory layout
- [ ] Write `.dse` output
- [ ] Add linker script support (basic)
- [ ] Comprehensive error messages
- [ ] Test suite with complex linking scenarios
---
### 1.2 Assembler Rewrite
> **Priority: HIGH** — Required for all compiled code.
> **Total Estimate: 1.5 weeks**
---
#### 1.2.1 Assembler Architecture Design
**Estimate: 1 day**
**Dependencies:** 1.1.1
**Deliverable:** `docs/assembler-architecture.md`
- [ ] Design multi-pass architecture
- [ ] Pass 1: Symbol collection
- [ ] Pass 2: Macro expansion
- [ ] Pass 3: Code generation
- [ ] Pass 4: Relocation generation
- [ ] Plan error handling strategy
- [ ] Design threading model for parallel file processing
- [ ] Define module/import resolution system
- [ ] Plan integration points with DSC compiler
---
#### 1.2.2 Implement Core Assembler
**Estimate: 5 days**
**Dependencies:** 1.1.2, 1.2.1
**Deliverable:** `dsa-asm` executable v2.0.0
- [ ] Create new crate: `dsa-assembler-ng` (next-gen)
- [ ] Implement lexer with better error recovery
- [ ] Build parser with detailed error messages
- [ ] Instruction parsing
- [ ] Directive handling
- [ ] Macro system
- [ ] Include resolution
- [ ] Symbol table management
- [ ] Code generator outputting to DSB format
- [ ] Multi-threading for file parsing
- [ ] Comprehensive test suite
- [ ] Error message testing
---
#### 1.2.3 Import System & DSC Integration
**Estimate: 2 days**
**Dependencies:** 1.2.2, 2.1.2
**Deliverable:** Working import system
- [ ] Design import protocol between DSC and assembler
- [ ] Implement symbol table merging
- [ ] Handle pre-compiled object imports
- [ ] Test DSC → Assembly → Object pipeline
- [ ] Document integration process
---
### 1.3 Documentation Updates
> **Priority: MEDIUM** — Can be done alongside development.
> **Total Estimate: 3 days (distributed)**
---
#### 1.3.1 Update Assembly Documentation
**Estimate: 1 day**
**Dependencies:** 1.2.2
**Deliverable:** Updated `docs/dsa-assembly-reference.md`
- [ ] Review all instruction documentation
- [ ] Document new pseudo-instructions
- [ ] Update calling convention docs
- [ ] Add examples for new features
- [ ] Document assembler directives
- [ ] Macro system documentation
---
#### 1.3.2 Architecture Documentation
**Estimate: 1 day**
**Dependencies:** None (can start anytime)
**Deliverable:** `docs/dsa-architecture.md`
- [ ] Document ISA specification
- [ ] Memory model documentation
- [ ] Interrupt handling
- [ ] Hardware peripheral specs
- [ ] Timing/performance characteristics
---
#### 1.3.3 Build Tools Documentation
**Estimate: 1 day**
**Dependencies:** 1.2.2, 1.1.3, 3.1.2
**Deliverable:** `docs/build-tools-guide.md`
- [ ] Assembler usage guide
- [ ] Linker usage guide
- [ ] Build system guide
- [ ] Tutorial: Building a simple program
- [ ] Tutorial: Multi-file projects
---
## Phase 2: Compiler Development
**Estimated Duration: 34 weeks**
---
### 2.1 Language Design & Implementation
> **Priority: HIGH** — Core functionality.
> **Total Estimate: 2.5 weeks**
---
#### 2.1.1 Language Syntax Design
**Estimate: 2 days**
**Dependencies:** None
**Deliverable:** `docs/language-spec.md`
- [x] Define syntax goals (simplicity, systems programming)
- [ ] Design type system
- [x] Primitive types
- [x] Pointers/references
- [ ] Structs
- [ ] Arrays
- [x] Function types
- [x] Control flow syntax
- [x] Function declaration syntax
- [x] Module/import system
- [x] Operator precedence
- [ ] Write EBNF grammar
- [x] Create example programs
---
#### 2.1.2 Lexer & Parser Implementation
**Estimate: 4 days**
**Dependencies:** 2.1.1
**Deliverable:** Parser in `dsc-compiler` crate
- [x] Adapt existing C lexer to new syntax
- [ ] Implement new parser for designed syntax
- [ ] Array syntax
- [ ] Struct syntax
- [x] Pointer syntax
- [x] Namespaced call syntax
- [x] AST node definitions
- [ ] Error recovery mechanisms
- [ ] Comprehensive parser tests
- [ ] Syntax error message quality testing
- [x] Implement C frontend by moving lexer/parser from `c_compiler` to the new `compiler` project structure
- [ ] Evaluate possible memory management strategies (e.g., keep all variables on the stack vs spill only when calling functions)
---
#### 2.1.3 Code Generation Improvements
**Estimate: 5 days**
**Dependencies:** 2.1.2, 1.2.2
**Deliverable:** Working code generator
- [x] Review and fix existing codegen issues
- [ ] Implement missing language features
- [ ] Structs
- [ ] Arrays
- [x] Pointers/memory operations
- [ ] For loops
- [ ] Switch statements
- [ ] Break/continue
- [ ] Optimize register allocation further
- [x] Implement proper function calling conventions
- [ ] Add constant folding optimization
- [x] Dead code elimination
- [ ] Test each feature thoroughly
---
#### 2.1.4 Type Checking & Semantic Analysis
**Estimate: 3 days**
**Dependencies:** 2.1.2
**Deliverable:** Type checker integrated in compiler
- [ ] Implement type checker
- [ ] Symbol table for scoping
- [ ] Type inference where applicable
- [ ] Const checking
- [ ] Definite assignment analysis
- [ ] Comprehensive semantic error messages
- [ ] Test suite for type errors
---
### 2.2 Standard Library
> **Priority: MEDIUM** — Needed for useful programs.
> **Total Estimate: 1 week**
---
#### 2.2.1 Core Runtime Library (in Assembly)
**Estimate: 3 days**
**Dependencies:** 1.2.2
**Deliverable:** `lib/runtime/` directory
- [ ] Memory allocation (malloc/free)
- [ ] String operations
- [ ] Math functions
- [x] Multiply
- [ ] Divide (fix as very slow and broken)
- [ ] I/O functions (improved print, read)
- [x] Print number
- [x] Print hex value
- [x] Print word
- [x] Print byte
- [x] Print from string ptr
- [x] Print whitespace and newline
- [x] Reset display
- [x] Reset cursor
- [ ] System call interface
- [ ] Tests for each function
---
#### 2.2.2 Standard Library (in DSC)
**Estimate: 2 days**
**Dependencies:** 2.1.3, 2.2.1
**Deliverable:** `lib/std/` directory
- [ ] String module
- [ ] Collections (array utilities, maybe simple list)
- [ ] File I/O module
- [ ] Math utilities
- [ ] Tests and examples
---
## Phase 3: Build System & Package Management
**Estimated Duration: 23 weeks**
---
### 3.1 Build System
> **Priority: HIGH** — Required for complex projects.
> **Total Estimate: 1.5 weeks**
---
#### 3.1.1 Build System Design
**Estimate: 1 day**
**Dependencies:** None
**Deliverable:** `docs/build-system-design.md`
- [x] Define project structure conventions
- [ ] Design build manifest format (`dsa-project.toml` or similar)
- [ ] Dependency resolution strategy
- [ ] Build cache design
- [ ] Incremental build strategy
- [ ] Multi-target support
---
#### 3.1.2 Build Tool Implementation
**Estimate: 5 days**
**Dependencies:** 3.1.1, 1.2.2, 1.1.3, 2.1.3
**Deliverable:** `dsa-build` executable
- [x] Create crate: `dsa-build`
- [ ] Manifest parser
- [ ] Dependency graph builder
- [ ] Task orchestrator
- [x] Compilation tasks
- [x] Assembly tasks
- [ ] Linking tasks
- [ ] Build cache implementation
- [ ] Parallel build support
- [ ] Clean, rebuild commands
- [ ] Watch mode for development
- [ ] Comprehensive tests
---
#### 3.1.3 Project Management Commands
**Estimate: 2 days**
**Dependencies:** 3.1.2
**Deliverable:** Enhanced `dsa-build` with project management
- [x] `dsa new <project>` — Create new project
- [x] `dsa init` — Initialize in existing directory
- [ ] `dsa add <dependency>` — Add dependency
- [ ] Binary vs library project types
- [x] Template system for project scaffolding
- [ ] Documentation for each command
---
### 3.2 Package Management System
> **Priority: MEDIUM** — Enables code sharing.
> **Total Estimate: 1.5 weeks**
---
#### 3.2.1 Package Registry Design
**Estimate: 2 days**
**Dependencies:** 3.1.1
**Deliverable:** `docs/package-registry-design.md`
- [ ] Decide: Git monorepo vs custom hosting
- [ ] Design package naming conventions
- [ ] Version resolution strategy (semver)
- [ ] Package manifest format
- [ ] Security considerations
- [ ] Package storage format (source/binary/both)
- [ ] API design for registry server
---
#### 3.2.2 Local Package Manager Tool
**Estimate: 4 days**
**Dependencies:** 3.2.1, 3.1.2
**Deliverable:** `dsa-pkg` tool integrated with `dsa-build`
- [ ] Create crate: `dsa-pkg`
- [ ] Package index synchronization
- [ ] Dependency resolver
- [ ] Package download/cache system
- [ ] Integration with build system
- [ ] Commands:
- [ ] `dsa install <package>`
- [ ] `dsa publish`
- [ ] `dsa search <query>`
- [ ] `dsa update`
- [ ] Lock file generation
- [ ] Test with mock registry
---
#### 3.2.3 Package Registry Implementation
**Estimate: 3 days**
**Dependencies:** 3.2.1
**Deliverable:** Package registry (URL or repo)
- [ ] If **Git monorepo** approach:
- [ ] Set up repository structure
- [ ] CI/CD for validation
- [ ] Submission process
- [ ] Package browser website
- [ ] If **custom hosting**:
- [ ] Simple web server (Rust + Axum/Actix)
- [ ] Package upload API
- [ ] Package search API
- [ ] Basic web UI
- [ ] Database for metadata
- [ ] Documentation for publishing
---
## Phase 4: Debugger & Development Tools
**Estimated Duration: 34 weeks**
---
### 4.1 Debug Symbol System
> **Priority: HIGH** — Foundation for debugging.
> **Total Estimate: 1 week**
---
#### 4.1.1 Debug Symbol Format Design
**Estimate: 1 day**
**Dependencies:** 1.1.1
**Deliverable:** `docs/debug-symbol-format.md`
- [ ] Design symbol table format
- [ ] Function addresses → names
- [ ] Line number → address mapping
- [ ] Variable location information
- [ ] Type information
- [ ] Define symbol table file format
- [ ] Plan for embedding in DSE/DSB files
---
#### 4.1.2 Symbol Generation in Tools
**Estimate: 3 days**
**Dependencies:** 4.1.1, 1.2.2, 2.1.3
**Deliverable:** Debug symbols in build output
- [ ] Modify assembler to emit debug symbols
- [ ] Modify compiler to emit debug symbols
- [ ] Source file/line tracking
- [ ] Variable scope tracking
- [ ] Linker merges debug symbols
- [ ] Test symbol generation pipeline
---
#### 4.1.3 Symbol Table Loader in Emulator
### Pre-Debugger Editor Integration Tasks
- **Integrate compiler into editor**
- Add a build command that invokes the full compiler pipeline (lexer → parser → codegen).
- Show compilation output and errors in the console panel.
- **DSC language support**
- Enable syntax highlighting and autocompletion for DSC files within the editor.
- Provide a dedicated “Build DSC” command that uses the integrated compiler.
- **Editor diagnostics**
- Wire compiler error messages to the editors gutter so users can click to jump to source lines.
**Estimate: 2 days**
**Dependencies:** 4.1.2
**Deliverable:** Symbol loading in emulator crate
- [ ] Implement symbol table parser
- [ ] Build address → symbol lookup (HashMap)
- [ ] Build symbol → address lookup
- [ ] Memory efficient storage
- [ ] Tests for symbol resolution
---
### 4.2 Debugger Implementation
> **Priority: HIGH** — Major productivity boost.
> **Total Estimate: 2 weeks**
---
#### 4.2.1 Core Debugger Features
**Estimate: 5 days**
**Dependencies:** 4.1.3
**Deliverable:** Debugger backend
- [ ] Execution control
- [ ] Step instruction
- [ ] Step over function calls
- [ ] Continue to breakpoint
- [ ] Run to cursor
- [ ] Breakpoint system
- [ ] Address breakpoints
- [ ] Conditional breakpoints
- [ ] Watchpoints (memory access)
- [ ] Register inspection
- [ ] Memory inspection
- [ ] Stack trace generation
- [ ] Test debugger commands
---
#### 4.2.2 Disassembler with Symbol Resolution
**Estimate: 3 days**
**Dependencies:** 4.1.3
**Deliverable:** Enhanced disassembler
- [ ] Instruction decoder
- [ ] Format with labels instead of addresses
- [ ] Show function names at call sites
- [ ] Inline comments with variable names
- [ ] Color coding for instruction types
- [ ] Tests for disassembly output
---
#### 4.2.3 Pseudo-Instruction Decompiler
> ⚠️ **COMPLEX TASK** — Separate pass to decompile assembly into readable pseudo-instructions.
**Estimate: 4 days**
**Dependencies:** 4.2.2
**Deliverable:** Pseudo-instruction view mode
- [ ] Pattern recognition for common sequences
- [ ] Function prologue/epilogue
- [ ] Multiplication using shifts/adds
- [ ] Division
- [ ] Conditional moves
- [ ] Control flow reconstruction
- [ ] If/else detection
- [ ] Loop detection
- [ ] Switch statement detection
- [ ] Expression reconstruction
- [ ] Format as higher-level pseudo-code
- [ ] Extensive pattern testing
---
#### 4.2.4 Execution History Tracking
**Estimate: 2 days**
**Dependencies:** 4.2.1
**Deliverable:** Execution trace feature
- [ ] Circular buffer for instruction history
- [ ] Register state snapshots over time
- [ ] Configurable history depth
- [ ] Efficient memory usage
- [ ] Playback/reverse debugging (basic)
- [ ] Export trace to file
---
### 4.3 Enhanced Editor Integration
> **Priority: MEDIUM** — UX improvement.
> **Total Estimate: 1 week**
---
#### 4.3.1 Tiling Window System
**Estimate: 2 days**
**Dependencies:** None (UI work)
**Deliverable:** Panel system in emulator
- [ ] Research Rust tiling libraries (`egui_tiles`, or custom)
- [ ] Design panel layout system
- [ ] Code editor panel
- [ ] Disassembly panel
- [ ] Register panel
- [ ] Memory panel
- [ ] Console panel
- [ ] Implement drag-and-drop panel management
- [ ] Save/load layouts
---
#### 4.3.2 Assembly Editor Improvements
**Estimate: 2 days**
**Dependencies:** 4.3.1
**Deliverable:** Enhanced assembly editor
- [ ] Syntax highlighting for DSA assembly
- [ ] Auto-completion for instructions
- [ ] Label/symbol auto-completion
- [ ] Error highlighting
- [ ] Inline documentation tooltips
- [ ] Jump-to-definition for labels
---
#### 4.3.3 High-Level Language Editor
**Estimate: 2 days**
**Dependencies:** 4.3.1, 2.1.4
**Deliverable:** DSC language editor
- [ ] Syntax highlighting for DSC language
- [ ] Basic auto-completion
- [ ] Bracket matching
- [ ] Error highlighting from compiler
- [ ] Go-to-definition (using debug symbols)
- [ ] Inline type hints
---
#### 4.3.4 Integrate Build Tools and Compiler into Editor
Estimate: 1 day
Dependencies: 4.3.1, 3.1.2, 2.1.2
Deliverable: Integrated build experience with compiler support
- [ ] Build button/command in UI that invokes the full compiler pipeline
- [ ] Show build output and compilation errors in console panel
- [ ] Error navigation (click to jump to source)
- [ ] Hot reload on successful build
- [ ] Build status indicator
- [ ] Hook DSC language support into editor for syntax highlighting and autocompletion
- [ ] Provide dedicated DSC build command that uses the new compiler integration
---
## Phase 5: Integration & Polish
**Estimated Duration: 12 weeks**
---
### 5.1 Tool Integration
> **Priority: HIGH** — Everything works together.
> **Total Estimate: 1 week**
---
#### 5.1.1 Unified Toolchain
**Estimate: 3 days**
**Dependencies:** All previous phases
**Deliverable:** `dsa` unified command-line tool
- [ ] Create meta-crate: `dsa-tools`
- [ ] Unified CLI with subcommands
- [ ] `dsa build`
- [ ] `dsa run`
- [ ] `dsa debug`
- [ ] `dsa test`
- [ ] `dsa pkg`
- [ ] Shared configuration system
- [ ] Tool interop testing
- [ ] Documentation for workflow
---
#### 5.1.2 Emulator Integration
**Estimate: 2 days**
**Dependencies:** 5.1.1, 4.3.4
**Deliverable:** Fully integrated development environment
- [ ] Add build tools as emulator dependencies
- [ ] In-editor build triggered from emulator
- [ ] Debugger uses build output directly
- [ ] Source-level debugging with line mapping
- [ ] Test full edit → build → debug cycle
---
#### 5.1.3 Documentation & Tutorials
**Estimate: 2 days**
**Dependencies:** 5.1.2
**Deliverable:** Complete documentation suite
- [ ] Getting started guide
- [ ] Full tutorial: Building a simple game
- [ ] Debugger usage guide
- [ ] Best practices document
- [ ] Troubleshooting guide
---
## Phase 6: Future Enhancements (NTH)
> **Priority: LOW** — Nice to have, long-term goal.
> **Estimated Duration: 4+ weeks**
---
### 6.1 Command-Line Emulator
> ⚠️ **COMPLEX LONG-TERM GOAL** — Requires significant design and UX consideration.
---
#### 6.1.1 Design Phase
**Estimate: 1 week**
**Dependencies:** None
**Deliverable:** `docs/cli-emulator-design.md`
- [ ] UX research for terminal-based debuggers
- [ ] Design TUI layout (using `ratatui` or similar)
- [ ] Command syntax design
- [ ] Scripting support design
- [ ] Accessibility considerations
---
#### 6.1.2 Implementation
**Estimate: 3+ weeks**
**Dependencies:** 6.1.1, Phase 4 complete
**Deliverable:** `dsa-emu-cli` executable
- [ ] TUI framework setup
- [ ] Core emulator integration
- [ ] Command parser
- [ ] Panel rendering (code, registers, memory, etc.)
- [ ] Keyboard shortcuts
- [ ] Mouse support
- [ ] Configuration system
- [ ] Extensive usability testing
---
## Summary Timeline
| Phase | Duration | Key Dependencies |
| ----------------------------- | --------- | ------------------- |
| Phase 1: Foundation | 34 weeks | None |
| Phase 2: Compiler | 34 weeks | Phase 1 complete |
| Phase 3: Build System | 23 weeks | Phases 12 complete |
| Phase 4: Debugger | 34 weeks | Phases 13 complete |
| Phase 5: Integration | 12 weeks | Phases 14 complete |
| Phase 6: CLI Emulator _(NTH)_ | 4+ weeks | Phase 4 complete |
**Total Estimated Time: 1217 weeks (34 months) for Phases 15**
---
## Critical Path
The following tasks are on the critical path and will block other work if delayed:
```
1.1.1 Binary format design
└── 1.1.2 Object file writer
└── 1.1.3 Linker
└── 1.2.2 Assembler rewrite
└── 2.1.3 Compiler codegen
└── 3.1.2 Build system
└── 4.1.2 Debug symbols
└── 4.2.1 Debugger
```
---
## Recommended Work Order
| Weeks | Focus | Tasks |
| ----- | ------------------------------------- | ------------------------------------------------- |
| 12 | Binary Format & Linker | 1.1.1 → 1.1.2 → 1.1.3 |
| 34 | Assembler Rewrite | 1.2.1 → 1.2.2 |
| 56 | Compiler Syntax & Parser | 2.1.1 → 2.1.2 _(start 1.3 docs in parallel)_ |
| 79 | Compiler Codegen & Types | 2.1.3 → 2.1.4 _(start 2.2.1 runtime in parallel)_ |
| 1011 | Build System | 3.1.1 → 3.1.2 → 3.1.3 |
| 1213 | Package Management _(if desired now)_ | 3.2.1 → 3.2.2 → 3.2.3 |
| 1415 | Debug Symbols | 4.1.1 → 4.1.2 → 4.1.3 |
| 1618 | Core Debugger | 4.2.1 → 4.2.2 → 4.2.4 |
| 1920 | Editor Enhancements | 4.3.1 → 4.3.2 → 4.3.3 → 4.3.4 |
| 2122 | Integration & Polish | 5.1.1 → 5.1.2 → 5.1.3 |
---
## Notes
- Time estimates assume ~68 productive hours per day.
- Add **2030% buffer** for unexpected issues.
- Testing time is included in each estimate.
- Documentation is distributed throughout rather than batched at the end.
- Package management (3.2) can be deferred if time-constrained.
- Pseudo-instruction decompiler (4.2.3) can be a stretch goal.
- CLI emulator (Phase 6) is explicitly a "nice to have" and should not block other work.
Binary file not shown.
-638
View File
@@ -1,638 +0,0 @@
# DSA Implementation vs Documentation Discrepancies
## Critical Discrepancies
### 1. **Stack Growth Direction** ❌ CRITICAL
**Documentation states:** Stack grows upward (toward higher addresses)
**Implementation shows (expand.rs:44-51):**
```rust
fn expand_push(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
// ...
nodes.extend(vec![
node!(label, Opcode::SubI, spr, 4, spr), // spr = spr - 4
node!(None, Opcode::Stw, reg, spr, 0),
]);
```
**Implementation shows (expand.rs:130-137):**
```rust
fn expand_pop(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
// ...
nodes.extend(vec![
node!(label, Opcode::Ldw, spr, reg, 0),
node!(None, Opcode::AddI, spr, 4, spr), // spr = spr + 4
]);
```
**Reality:** Stack grows **DOWNWARD** (toward lower addresses)
- PUSH: Decrements SPR by 4, then stores
- POP: Loads, then increments SPR by 4
**Impact:** All documentation examples and calling convention diagrams are backwards!
---
### 2. **CALL Pseudo-instruction Expansion** ❌ CRITICAL
**Documentation states (DSA_Assembly_Reference.md):**
```asm
; call print::print expands to:
lwi print::print, ret ; Load function address into ret
jmp 0, ret ; Jump to function (saves return in pcx)
```
**Implementation shows (expand.rs:109-123):**
```rust
fn expand_call(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
nodes.extend(vec![
node!(label, Opcode::SubI, spr, 4, spr), // Decrement stack pointer
node!(None, Opcode::Stw, pcx, spr, 0), // Store PCX (return addr) on stack
node!(None, Opcode::Jmp, addr, zero), // Jump to function
]);
```
**Reality:** CALL expansion is:
1. Decrement SPR by 4
2. Store PCX (return address) to stack
3. Jump to function address
**Impact:** Return address is stored on the STACK, not in RET register!
---
### 3. **RETURN Pseudo-instruction Expansion** ❌ CRITICAL
**Documentation states:**
```asm
; return expands to:
jmp 0, ret ; Jump to address in ret register
```
**Implementation shows (expand.rs:125-135):**
```rust
fn expand_return(current: &Node, nodes: &mut Vec<Node>) {
nodes.extend(vec![
node!(label, Opcode::Ldw, spr, ret, 0), // Load return addr from stack
node!(None, Opcode::AddI, spr, 4, spr), // Increment stack pointer
node!(None, Opcode::Jmp, 4, ret), // Jump to (ret + 4)
]);
}
```
**Reality:** RETURN expansion is:
1. Load return address from stack into RET register
2. Increment SPR by 4
3. Jump to (RET + 4)
**Why +4?** The stored PCX points to the instruction AFTER the call's jump, so we need to add 4 to skip past the stored PCX instruction itself... or this might be a bug in the implementation.
**Impact:** Return mechanism is completely different from documentation!
---
### 4. **Calling Convention - Stack Frame Layout** ❌ CRITICAL
**Documentation states:**
```
Higher Addresses
├─────────────┤
│ Arg N │ ← spr + (8 + 4*(N-1))
│ ... │
│ Arg 2 │ ← spr + 16
│ Arg 1 │ ← spr + 12
│ Arg 0 │ ← spr + 8
├─────────────┤
│ Ret Addr │ ← spr + 4
├─────────────┤
│ Old BPR │ ← spr + 0
├─────────────┤ ← bpr, spr
│ Locals │
Lower Addresses
```
**Reality based on implementation:**
Since stack grows DOWN:
```
Lower Addresses
├─────────────┤ ← Current SPR/BPR
│ Old BPR │ ← spr + 0 (immediately above SPR)
├─────────────┤
│ Ret Addr │ ← spr + 4 (pushed by CALL)
├─────────────┤
│ Arg 0 │ ← spr + 8
│ Arg 1 │ ← spr + 12
│ Arg 2 │ ← spr + 16
│ ... │
│ Arg N │ ← spr + (8 + 4*(N-1))
├─────────────┤
Higher Addresses
```
**The diagram needs to be flipped!** The offsets are correct, but the direction is wrong.
---
### 5. **Label-Based Load/Store Scratch Register** ⚠️ IMPORTANT
**Documentation states:** Uses `rgf` as scratch register
**Implementation confirms (expand.rs:138-153):**
```rust
fn expand_ldx(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
// For ldb label, reg:
nodes.extend(vec![
node!(current.label(), Opcode::Lli, name, reg),
node!(None, Opcode::Lui, name, reg),
node!(None, opcode, reg, reg, offset),
]);
```
**Wait! This is WRONG in the implementation!**
The load expansion uses the DESTINATION register as scratch:
```asm
ldb buffer, rg2 expands to:
lli buffer, rg2 ; Uses rg2 as destination
lui buffer, rg2 ; Uses rg2 as destination
ldb rg2, rg2, 0 ; Uses rg2 as base
```
**Documentation says it should use rgf:**
```asm
ldb buffer, rg2 expands to:
lli buffer, rgf ; Uses rgf as scratch
lui buffer, rgf ; Uses rgf as scratch
ldb rgf, rg2, 0 ; Load from rgf into rg2
```
**For stores (expand.rs:155-176):**
```rust
fn expand_stx(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
// For stb reg, label:
let temp = Token::Register(Register::Acc); // Uses ACC, not RGF!
nodes.extend(vec![
node!(current.label(), Opcode::Lli, dest, temp),
node!(None, Opcode::Lui, dest, temp),
node!(None, opcode, base, temp, offset),
]);
```
**Reality:**
- Load pseudo-instructions use the DESTINATION register as scratch
- Store pseudo-instructions use the ACC register as scratch, NOT rgf
**Impact:** Documentation is incorrect about which registers are used!
---
### 6. **LWI Pseudo-instruction** ✅ CORRECT
**Documentation and implementation agree:**
```rust
fn expand_lwi(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
nodes.extend(vec![
node!(current.label(), Opcode::Lli, val, reg),
node!(None, Opcode::Lui, val, reg),
]);
```
This matches the documented expansion.
---
### 7. **PUSHA/POPA Pseudo-instructions** 📝 UNDOCUMENTED
**These exist in implementation but are NOT in documentation!**
**expand.rs:53-76:**
```rust
fn expand_pusha(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
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)
)
}));
```
**expand.rs:78-101:**
```rust
fn expand_popa(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let count = expect_token!(arg0, Immediate)?;
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));
```
**What they do:**
- `pusha N` - Push first N general-purpose registers (rg0-rgN) to stack
- `popa N` - Pop first N general-purpose registers from stack
**Missing from documentation entirely!**
---
### 8. **Register Index Encoding** ⚠️ IMPORTANT
**Documentation states:** System registers like MAR, MDR, STS, CIR, PCX are "internal" and not accessible
**Implementation shows (instructions.rs:148-153):**
```rust
0x18 => Self::Mar,
0x19 => Self::Mdr,
0x1A => Self::Sts,
0x1B => Self::Cir,
0x1C => Self::Pcx,
```
**Reality:** These registers ARE encoded in the instruction format at indices 0x18-0x1C!
**However, instructions.rs:186 shows:**
```rust
"null" => Ok(Self::NoReg), // Can parse "null" as NoReg
```
**Documentation never mentions "null" as an alternative name for noreg!**
---
### 9. **LUI Immediate Value Handling** ⚠️ IMPORTANT
**Documentation states:**
```
lui immediate, dest_reg ; Load immediate into upper 16 bits
```
**Implementation shows (codegen.rs:248-254):**
```rust
fn build_load_immediate_instruction(...) -> Result<Instruction, AssembleError> {
// ...
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; // Shifts right by 16!
let instruction_args = args!(I, immediate: upper_value as u16, r1: dest);
Ok(Instruction::LoadUpperImmediate(instruction_args))
}
```
**Reality:** When assembling `lui immediate, reg`, the assembler:
1. Takes the immediate value
2. Shifts it RIGHT by 16 bits
3. Stores the result in the instruction
**This means:**
```asm
lli 0x1234, rg0 ; Stores 0x1234 in lower 16 bits
lui 0xABCD0000, rg0 ; Right-shifts to 0xABCD, stores in upper 16 bits
```
**Or more likely, the assembler expects:**
```asm
lli 0x1234, rg0 ; Stores 0x1234 in lower 16 bits
lui 0xABCD, rg0 ; Stores 0xABCD in upper 16 bits (no shift needed)
```
**Documentation needs clarification on what immediate value format LUI expects!**
---
### 10. **Data Definition Encoding** ⚠️ IMPORTANT
**Implementation (expand.rs:217-267):**
```rust
fn process_dx_data(args: Vec<Token>, size: usize) -> Result<Vec<u32>, AssembleError> {
for token in args {
match token {
Token::StringLit(mut s) => {
s.push('\0'); // Automatically adds null terminator!
for ch in s.chars() {
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) => {
buffer.extend_from_slice(&value.to_be_bytes()); // BIG ENDIAN!
}
```
**Key findings:**
1. String literals automatically get null terminator appended
2. Numeric values are stored in **BIG ENDIAN** format (to_be_bytes)
3. Documentation says "little-endian byte order" globally
**Contradiction:** Data definition uses BIG ENDIAN, but doc says LITTLE ENDIAN!
---
### 11. **Segment Instruction** 📝 UNDOCUMENTED
**Implementation has a SEGMENT instruction (0x27/0x3F):**
```rust
Segment(u32) = 0x3F,
```
**This is completely undocumented!**
From model.rs:
```rust
Self::Segment => write!(f, "[SEGMENT]"),
```
From codegen.rs:
```rust
Opcode::Segment => build_segment_instruction(&args),
```
**Purpose unclear, needs documentation!**
---
### 12. **Data Instruction** 📝 UNDOCUMENTED
**Implementation has a DATA instruction (0x3E):**
```rust
Data(u32) = 0x3E,
```
**This appears to be a meta-instruction for embedding raw data, but it's undocumented in the assembly reference!**
---
### 13. **INC/DEC Instruction Encoding** ⚠️ MINOR
**Implementation (codegen.rs:293-299):**
```rust
fn build_inc_dec_instruction(opcode: Opcode, args: &[Token]) -> Result<Instruction, AssembleError> {
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))),
```
**Reality:** INC/DEC only set SR1 field, not DR field.
**But args.rs shows:**
```rust
impl RTypeArgs {
pub fn new(...) -> Self {
let sr1 = sr1.unwrap_or_default(); // Defaults to NoReg
let dr = dr.unwrap_or_default(); // Defaults to NoReg
```
**So the DR field gets set to NoReg, which is correct per documentation.**
**However, the Display impl (instructions.rs:449) shows:**
```rust
Self::Increment(a) | Self::Decrement(a) => write!(f, " {}", a.sr1),
```
**This is correct - only shows SR1 in disassembly.**
---
### 14. **Shift Instruction Operand Order** ⚠️ MINOR
**Implementation (codegen.rs:301-312):**
```rust
fn build_shift_instruction(opcode: Opcode, args: &[Token]) -> Result<Instruction, AssembleError> {
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))),
```
**This only handles LITERAL shift amounts, not REGISTER shift amounts!**
**Documentation states both are supported:**
```asm
shl rg0, 2 ; Literal shift
shl rg0, rg1 ; Register shift
```
**The current codegen only handles the literal case!**
**This is a BUG in the implementation - register shifts aren't properly assembled!**
---
### 15. **Jump Instruction Operand Order** ⚠️ CONFUSION
**Documentation shows assembly syntax:**
```asm
jmp addr [, offset_reg]
```
**But implementation (codegen.rs:256-270):**
```rust
fn build_jump_instruction(opcode: Opcode, args: &[Token]) -> Result<Instruction, AssembleError> {
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);
```
**This expects:**
1. First arg: immediate (address)
2. Second arg: register (offset)
**So assembly syntax should be:**
```asm
jmp immediate, offset_register
```
**Example:**
```asm
jmp 0x1000, zero ; Jump to 0x1000
jmp 4, ret ; Jump to (ret + 4)
```
**Documentation syntax is correct, but parameter names are confusing!**
The "address" is actually an OFFSET, and the register is the BASE!
**Better naming:**
```asm
jmp offset, base_register
; Target = base_register + offset
```
---
### 16. **NOT Instruction Operand Count** ✅ MINOR ISSUE
**Documentation shows:**
```asm
not src, dest ; Two operands
```
**Implementation (instructions.rs:428-429):**
```rust
Self::Compare(args) | Self::Not(args) => {
write!(f, " {}, {}", args.sr1, args.sr2)
}
```
**This displays BOTH sr1 and sr2 for NOT!**
**But codegen.rs:354-362:**
```rust
fn build_not_instruction(args: &[Token]) -> Result<Instruction, AssembleError> {
let reg = expect_token!(reg_token, Register)?;
let dest = expect_token!(dest_token, Register)?;
Ok(Instruction::Not(args!(R, sr1: reg, dr: dest)))
```
**Sets sr1 and dr, NOT sr1 and sr2!**
**The Display impl is WRONG - should show sr1 and dr:**
```rust
Self::Not(args) => write!(f, " {}, {}", args.sr1, args.dr)
```
**This is a display bug in the implementation!**
---
### 17. **Register File Indexing** ✅ CORRECT
**Documentation and implementation both agree:**
- 0x00-0x0F: rg0-rgf (general purpose)
- 0x10: acc
- 0x11: spr
- 0x12: bpr
- 0x13: ret
- 0x14: idr
- 0x15: mmr
- 0x16: zero
- 0x17: noreg
**This matches perfectly.**
---
### 18. **Immediate Arithmetic Destination** ⚠️ MINOR
**Implementation (codegen.rs:314-330):**
```rust
fn build_arithmetic_immediate_instruction(...) -> Result<Instruction, AssembleError> {
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);
```
**This REQUIRES three arguments:**
1. Source register
2. Immediate value
3. Destination register
**But documentation says destination is optional:**
```
iadd src_reg, imm [, dest_reg] ; dest optional
```
**Reality:** The assembler REQUIRES the destination register!
**If you want in-place operation:**
```asm
iadd rg0, 10, rg0 ; Required to specify rg0 twice
```
**Not:**
```asm
iadd rg0, 10 ; This won't work!
```
**Documentation is misleading - destination is NOT optional!**
---
### 19. **Memory Instruction Offsets** ✅ CORRECT
**Implementation correctly handles signed 16-bit offsets:**
```rust
let offset = expect_token!(offset_token, Immediate)? as u16;
```
**These are stored as u16 but interpreted as signed i16 at runtime.**
**Documentation is correct about this.**
---
### 20. **Instruction Opcode Values** ✅ VERIFIED
Comparing model.rs opcodes with instructions.rs:
| Instruction | model.rs | instructions.rs | Match |
|-------------|----------|-----------------|-------|
| Nop | 0x00 | 0x0 | ✅ |
| Mov | 0x01 | 0x1 | ✅ |
| MovSigned | 0x02 | 0x2 | ✅ |
| LoadByte | 0x03 | 0x3 | ✅ |
| ... | ... | ... | ✅ |
| AddImmediate | 0x25 | 0x25 | ✅ |
| SubImmediate | 0x26 | 0x26 | ✅ |
| Segment | 0x27 | 0x3F | ❌ MISMATCH! |
**CRITICAL:** Segment instruction has opcode **0x27** in model.rs but **0x3F** in instructions.rs!
---
## Summary of Critical Issues
### Must Fix in Documentation:
1.**Stack grows DOWNWARD** - flip all diagrams
2.**CALL expansion** - uses stack, not ret register directly
3.**RETURN expansion** - loads from stack, jumps to ret+4
4.**Stack frame layout** - flip diagram vertically
5.**Load pseudo scratch register** - uses DEST reg, not rgf
6.**Store pseudo scratch register** - uses ACC, not rgf
7.**Add PUSHA/POPA documentation**
8.**Add SEGMENT instruction documentation**
9.**Add DATA instruction documentation**
10.**Clarify LUI immediate value handling**
11.**Fix endianness** - data definition uses BIG endian
12.**IADD/ISUB destination NOT optional**
13.**Add "null" as alias for noreg**
14.**Fix Segment opcode** - 0x27 or 0x3F?
### Potential Implementation Bugs:
1. ⚠️ **Shift instruction** - doesn't handle register shifts
2. ⚠️ **NOT display** - shows sr2 instead of dr
3. ⚠️ **RETURN +4 offset** - why is this needed?
4. ⚠️ **Segment opcode mismatch** - 0x27 vs 0x3F
### Minor Documentation Improvements:
1. Add explicit examples of stack growth direction
2. Show complete memory layout diagrams
3. Document which registers are volatile/preserved
4. Add troubleshooting section for common mistakes
5. Clarify jump instruction parameter semantics
-4
View File
@@ -1,4 +0,0 @@
- we definitely need to be able to use registers for shift operations.
- we need logical boolean operations in addition to the bitwise ones.
- better conditionals.
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
-149
View File
@@ -1,149 +0,0 @@
# DSA Documentation Inconsistencies Analysis
## 1. Register Descriptions
### Issue: System Registers vs Assembly-Accessible Registers
- `registers.md` lists MAR, STS, CIR, MDR as "System" registers
- These are NOT mentioned in `dsa_assembly_reference.md` or `instruction_set.md`
- **Resolution**: System registers are internal CPU registers not directly accessible in assembly. They should be documented separately from programmer-accessible registers.
### Issue: Register Naming Inconsistencies
- `registers.md` uses `RG0-RGF` (uppercase)
- `dsa_assembly_reference.md` uses `rg0-rgf` (lowercase)
- **Resolution**: Assembly syntax should be lowercase (standard convention)
### Issue: NOREG Register
- `registers.md`: "Loads/using as dest register must cause an illegal instruction trap"
- `dsa_assembly_reference.md`: "on-read/write: illegal instruction fault"
- **Resolution**: Consistent terminology needed - use "illegal instruction fault"
## 2. Instruction Operand Order Inconsistencies
### Issue: Load Instructions
- `instruction_set.md`: `LDB BaseReg, Offset, DestReg`
- `dsa_assembly_reference.md`: `LDB base_reg, dest_reg [, offset]`
- **Resolution**: Assembly reference shows standard syntax (base, dest, offset optional), instruction set shows encoding order
### Issue: Store Instructions
- `instruction_set.md`: `STB SrcReg, BaseReg, Offset`
- `dsa_assembly_reference.md`: `STB src_reg, base_reg [, offset]`
- **Resolution**: Consistent - offset is optional
### Issue: Immediate Load Instructions
- `instruction_set.md`: `LLI DstReg, Value` (destination first)
- `dsa_assembly_reference.md`: `LLI imm, dest_reg` (immediate first)
- **Resolution**: Assembly reference shows gas-style syntax (source, dest), instruction set shows encoding order
### Issue: Jump Instructions
- `instruction_set.md`: `JMP DestReg, Offset | Address`
- `dsa_assembly_reference.md`: `JMP addr [, offset_reg]` or `JMP imm, offset_reg`
- **Resolution**: Different perspectives - instruction set shows encoding, assembly shows usage
## 3. Instruction Behavior Differences
### Issue: IADD/ISUB Operands
- `instruction_set.md`: `IADD Src1, Literal, Dest` (3 operands)
- `dsa_assembly_reference.md`: `IADD src_reg, imm [, dest_reg]` (dest optional)
- **Resolution**: Assembly allows dest to default to src_reg
### Issue: SHL/SHR Operands
- `instruction_set.md`: `SHL Reg, Literal | ValReg`
- `dsa_assembly_reference.md`: `SHL reg, shift_amount`
- **Resolution**: Both literal and register shifts supported
## 4. Pseudo-Instruction Inconsistencies
### Issue: PUSH/POP Expansion
- `pseudoinstructions.md`:
- PUSH = `INC SPR` then `STW register, SPR`
- POP = `LDW SPR, register` then `DEC SPR`
- Standard stack conventions suggest PUSH should decrement (grow down)
- **Resolution**: Clarify stack growth direction
### Issue: LDB/LDH/LDW Pseudo vs Hardware
- `pseudoinstructions.md` lists LDB, LDH, LDW as pseudo-instructions with label addressing
- `instruction_set.md` lists them as hardware instructions
- **Resolution**: Both exist - hardware instructions use registers, pseudo-instructions add label support
### Issue: LWI Naming
- `dsa_assembly_reference.md`: LWI = Load Word Immediate (load address)
- Could be confused with "Load Word Immediate" (load literal value)
- **Resolution**: LWI specifically means "Load Word address Into register"
## 5. Calling Convention Details
### Issue: Argument Offsets
- Calling convention says "first 3 args at offsets 8, 12, 16"
- This assumes 32-bit words (4 bytes each)
- Offset 8 is position of first argument (after return address at offset 4, and old BPR at offset 0)
- **Resolution**: Clarify that SPR+0 = old BPR, SPR+4 = return address, SPR+8 = first arg
### Issue: Return Value Location
- Says "Store return value (if any) to `spr+8`"
- This overwrites the first argument
- **Resolution**: This is intentional - return value replaces first argument position after cleanup
## 6. Missing Information
### From instruction_set.md not in assembly reference:
- Instruction encoding details (R-type, I-type, J-type)
- Hex opcodes for each instruction
- Alignment requirements for memory operations
- Sign extension behavior details
### From assembly reference not in instruction_set:
- Complete pseudo-instruction expansions showing what they compile to
- Library examples (multiply, print)
- Detailed calling convention walkthrough
- Module system (INCLUDE directive)
### From registers.md not elsewhere:
- STS (Status Register) bit layout
- Boot values for status flags
- System registers (MAR, STS, CIR, MDR)
## 7. Terminology Inconsistencies
- "halfword" vs "half-word" vs "16-bit value"
- "word" assumed to be 32-bit (should be explicit)
- "register" vs "reg" in syntax
- "immediate" vs "literal" vs "constant"
## 8. Critical Missing Details
### CALL and RETURN Pseudo-instructions
- Assembly reference shows them but doesn't show their expansion
- Need to document what they expand to
### Label Addressing Mode
- Shows expansions for loads/stores with labels
- Uses RGF as scratch register - should this be documented as reserved for this purpose?
### Stack Direction
- Not explicitly stated whether stack grows up or down
- PUSH uses INC SPR (suggests growing up) - unusual!
## Recommendations
1. **Separate Documentation into Logical Layers**:
- ISA Specification (hardware-level, for CPU implementers)
- Assembly Language Reference (for programmers)
- ABI/Calling Convention (for compiler/linker writers)
2. **Standardize Terminology**:
- Use consistent casing (lowercase for assembly mnemonics)
- Define terms clearly (word = 32-bit, halfword = 16-bit, byte = 8-bit)
- Distinguish "literal" (immediate value in code) from "address" (memory location)
3. **Document Stack Convention Clearly**:
- Explicitly state stack grows upward (unusual but valid)
- Show memory layout diagrams
4. **Show Complete Pseudo-instruction Expansions**:
- CALL, RETURN need full expansion documentation
- Document which register(s) are used as temporaries
5. **Clarify Register Usage Conventions**:
- ACC: used by pseudo-instructions, volatile
- RGF: used by label addressing, volatile
- RG0-RGE: general purpose, callee may use per calling convention
-26
View File
@@ -1,26 +0,0 @@
# General TODO's
# Bugfixes
- [x] [EASY] Investigate logical and operator not compiling - either a lexer or parser issue.
- **note**: this was a parser issue.
# Missing features
- [x] [MEDIUM] Get shift operations working correctly.
- [ ] [MEDIUM] proper prefix/postfix inc/dec implementation. slightly more complex as we need to check for a variable and modify it in place
- [ ] [EASY] Add multiply and divide operations to code generation
- **note**: very easy to do but our division algorithm is hopelessly slow so not worth doing for now.
# Performance Improvements
- [ ] [MEDIUM] implement a proper div/mod library that's not slow af.
- [ ] [HARD] Immediate operations for values that support it (up to +/- u16::max for addi and subi respectively)
- this requires significant complexity in code generation as we need to traverse down the tree when we come across these operations to prevent additional register allocations.
# Compiler optimisations
# Codegen improvements
- [ ] [MEDIUM / time consuming] Add scoping to code generation
- [ ] [MEDIUM / time consuming] Rewrite entire codegen to imrpove code quality and make the code more readable.
- [ ] type-safe instruction builder
- [ ] Instruction & Register enums
- [ ] Instruction builder helper fns eg `fn add(left: &Register, right: &Register, dest: &Register) -> Instruction`
- [ ] Instruction Block types.
+3985
View File
File diff suppressed because it is too large Load Diff
-25
View File
@@ -1,25 +0,0 @@
use super::Syntax;
use std::collections::BTreeSet;
impl Syntax {
pub fn dsc() -> Self {
Syntax {
language: "Damn Simple Code",
case_sensitive: false,
comment: "//",
comment_multiline: ["/*", "*/"],
hyperlinks: BTreeSet::from(["http"]),
keywords: BTreeSet::from([
"include", "fn", "let", "const", "static", "if", "else", "while", "for",
"break", "continue", "loop", "return",
]),
types: BTreeSet::from([
"u32", "u16", "u8", "i32", "i16", "i8", "str", "char", "bool", "void",
]),
special: BTreeSet::from([
",", ";", ".", ":", "=", "+", "-", "*", "/", "%", "&", "|", "^", "~",
"!", "?", "<", ">", "<<", ">>", "==", "!=", "<=", ">=", "&&", "||",
]),
}
}
}
-1
View File
@@ -1,6 +1,5 @@
#![allow(dead_code)] #![allow(dead_code)]
pub mod dsa; pub mod dsa;
pub mod dsc;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
-10
View File
@@ -1,10 +0,0 @@
[package]
name = "dsx-build"
version.workspace = true
edition.workspace = true
authors.workspace = true
[dependencies]
compiler = { path = "../compiler" }
assembler = { path = "../assembler" }
chrono = "0.4.43"
-200
View File
@@ -1,200 +0,0 @@
use std::process::{Command, Stdio};
use std::{
env, fs,
path::{Path, PathBuf},
};
use crate::templates::{Dsa, Dsc, Template};
mod templates;
/// Run a command and exit on failure.
fn run(cmd: &mut Command) {
let status = cmd.status().expect("failed to execute command");
if !status.success() {
std::process::exit(1);
}
}
fn main() {
// Very small CLI only three subcommands.
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Usage: dsx-build <new|build|package> [options]");
std::process::exit(1);
}
match args[1].as_str() {
"new" => cmd_new(&args[2..]),
"build" => cmd_build(),
"package" => todo!("Package manager stub not implemented yet."),
_ => {
eprintln!("Unknown command: {}", args[1]);
std::process::exit(1);
}
}
}
// ---------- new project ----------------------------------------------------
fn cmd_new(args: &[String]) {
let mut lang = "dsa";
for i in 0..args.len() {
if args[i] == "--lang" && i + 1 < args.len() {
lang = &args[i + 1];
}
}
let lib = args.contains(&"--lib".to_string());
// Determine project root: a subdirectory named after the supplied --name argument.
let mut name_opt = None;
for i in 0..args.len() {
if args[i] == "--name" && i + 1 < args.len() {
name_opt = Some(&args[i + 1]);
break;
}
}
let project_name = match name_opt {
Some(name) => name.to_string(),
None => {
eprintln!("Error: --name argument required");
std::process::exit(1);
}
};
let cwd = env::current_dir().unwrap();
let src_path = cwd.join(&project_name).join("src");
fs::create_dir_all(&src_path).expect("Failed to create project directory");
match lang {
"dsa" => {
// Minimal DSA binary template.
let path = src_path.join(format!("main.dsa"));
let template = Dsa::create(&project_name, lib);
fs::write(path, template).expect("Unable to write DSA file");
}
"dsc" => {
let path = src_path.join(format!("main.dsc"));
let template = Dsc::create(&project_name, lib);
fs::write(path, template).expect("Unable to write DSC file");
}
_ => {
eprintln!("Unsupported language: {}", lang);
std::process::exit(1);
}
}
fs::create_dir_all(src_path.join("lib")).expect("Failed to create lib directory");
fs::write(
src_path.join("lib/print.dsa"),
templates::create_print_lib(),
)
.expect("Failed to create print.dsa");
fs::write(
src_path.join("lib/maths.dsa"),
templates::create_maths_lib(),
)
.expect("Failed to create maths.dsa");
println!(
"Created new {} project in {}.",
lang,
src_path.parent().unwrap().display()
);
}
// ---------- build ----------------------------------------------------------
fn cmd_build() {
let cwd = env::current_dir().unwrap();
// Detect .dsc or .dsa files in current directory.
let mut has_dsc = false;
let mut has_dsa = false;
for entry in fs::read_dir(&cwd.join("src")).expect("unable to read dir") {
if let Ok(entry) = entry {
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("dsc") {
has_dsc = true;
} else if path.extension().and_then(|s| s.to_str()) == Some("dsa") {
has_dsa = true;
}
}
}
if !has_dsc && !has_dsa {
eprintln!("No .dsc or .dsa source found in src directory.");
std::process::exit(1);
}
// Assemble main.dsa to a dsb binary.
println!("Assembling Project to a DSB binary...");
let build_dir = cwd.join("build");
fs::create_dir_all(&build_dir).expect("Failed to create build directory");
// Copy everything from `cwd/src` to the build directory.
fn copy_recursively(src: &Path, dst: &Path) {
if src.is_file() {
fs::create_dir_all(dst.parent().unwrap())
.expect("Failed to create parent directory");
fs::copy(src, dst).expect("Failed to copy file");
} else if src.is_dir() {
for entry in fs::read_dir(src).expect("Unable to read source dir") {
let entry = entry.expect("Failed to read entry");
let child_src = entry.path();
let child_dst = dst.join(entry.file_name());
copy_recursively(&child_src, &child_dst);
}
}
}
let src_dir = cwd.join("src");
if src_dir.exists() {
copy_recursively(&src_dir, &build_dir);
}
// Change current working directory to the build directory.
env::set_current_dir(&build_dir).expect("Failed to change to build directory");
if has_dsc {
println!("Compiling DSC to DSA...");
fn compile_recursive(path: &Path) {
if path.is_dir() {
for entry in fs::read_dir(path).expect("unable to read dir") {
let entry = entry.expect("failed to read entry");
compile_recursive(&entry.path());
}
} else if path.extension().and_then(|s| s.to_str()) == Some("dsc") {
let input_path = path;
let output_path = path.with_extension("dsa");
compiler::compile_file(&input_path, &output_path).unwrap_or_else(|e| {
eprintln!("Failed to compile {:?}: {}", input_path, e);
std::process::exit(1);
});
}
}
compile_recursive(&build_dir);
}
// Replace .dsc with .dsa only in include statements, recursively for each file.
let mut sed_cmd = Command::new("bash");
sed_cmd.args(&[
"-c",
&format!(
"find \"{}\" -type f -name '*.dsa' -exec sed -i '/^include/ s/\\.dsc/.dsa/g' {{}} +",
build_dir.display()
),
]);
run(&mut sed_cmd);
fs::create_dir_all(&cwd.join("artifacts")).expect("Failed to create build directory");
assembler::assemble_file("./main.dsa", "../artifacts/out.dsb").unwrap_or_else(|e| {
eprintln!("Failed to assemble {:?}: {}", "./main.dsa", e);
std::process::exit(1);
});
println!("Build finished. Binary at {}/main.dsb", build_dir.display());
}
-589
View File
@@ -1,589 +0,0 @@
pub trait Template {
fn lib(project: &str) -> String;
fn bin(project: &str) -> String;
fn create(project: &str, lib: bool) -> String {
if lib {
Self::lib(project)
} else {
Self::bin(project)
}
}
}
pub struct Dsa;
pub struct Dsc;
impl Template for Dsa {
fn lib(project: &str) -> String {
format!(
r#"//
lib.dsa
// usage:
//
// include {project} "<relative path>"
//
// usage for {project}_main:
// push (arg1)
// push (arg0)
// call {project}::{project}_main
// pop (arg0)
// pop (arg1)
// Example data declarations
// dw example_data: 0x0000
// Main function template
{project}_main:
// the correct way to start a function as defined by the calling convention
push bpr
mov spr, bpr
// explanation of how to access args
ldw bpr, rg0, 8 // arg 0
ldw bpr, rg0, 12 // arg 1
// your code goes here
// Example: load example_data into rg1
// ldw example_data, rg1
// the correct way to end a function as defined by the calling convention
mov bpr, spr
pop bpr
return
"#,
)
}
fn bin(project: &str) -> String {
format!(
r#"
// GENERATED BY DSX-BUILD
// Generated at: {timestamp}
// Project name: {project}
// Imports
include print: "./lib/print.dsa"
// Globals & Reserved Memory
dw stack: 0x10000
db message: "Process Exited with code:"
// Entry Point
_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
main:
push bpr
mov spr, bpr
// Your code goes here
// Return zero
stw zero, bpr, 8
mov bpr, spr
pop bpr
return"#,
timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string()
)
}
}
impl Template for Dsc {
fn lib(project: &str) -> String {
format!(
r#"
// GENERATED BY DSX-BUILD
// Generated at: {timestamp}
// Project name: {project}
// Imports
include print: "./lib/print.dsa";
// Main Function
fn {project}_main() -> u32 {{
return 0;
}}"#,
timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string()
)
}
fn bin(project: &str) -> String {
format!(
r#"
// GENERATED BY DSX-BUILD
// Generated at: {timestamp}
// Project name: {project}
// Imports
include print: "./lib/print.dsa";
// Main Function
fn main() -> u32 {{
return 0;
}}"#,
timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string()
)
}
}
pub fn create_print_lib() -> String {
format!(
r#"
// 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.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
"#
)
}
pub fn create_maths_lib() -> String {
format!(
r#"
// 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
"#
)
}
+1 -3
View File
@@ -16,15 +16,13 @@ required-features = ["config"]
[dependencies] [dependencies]
common = { path = "../common" } common = { path = "../common" }
assembler = { path = "../assembler" } assembler = { path = "../assembler" }
compiler = { path = "../compiler" }
dsa_editor = { path = "../dsa_editor" } dsa_editor = { path = "../dsa_editor" }
egui = "0.31.1" egui = "0.31.1"
dirs = "6.0.0" dirs = "6.0.0"
discord-presence = { version = "2.0.0", optional = true } discord-presence = { version = "1.6.0", optional = true }
toml = { version = "0.8.23", optional = true } toml = { version = "0.8.23", optional = true }
serde = { version = "1.0.219", features = ["derive"], optional = true } serde = { version = "1.0.219", features = ["derive"], optional = true }
egui_file = "0.22.1" egui_file = "0.22.1"
rustc-hash = "2.1.1"
[features] [features]
default = ["config"] default = ["config"]
+4 -4
View File
@@ -180,10 +180,10 @@ impl Drop for RpcClient {
fn drop(&mut self) { fn drop(&mut self) {
self.stop(); self.stop();
if let Some(handle) = self.thread_handle.take() { if let Some(handle) = self.thread_handle.take()
if let Some(handle) = Arc::into_inner(handle) { && let Some(handle) = Arc::into_inner(handle)
let _ = handle.join(); {
} let _ = handle.join();
} }
} }
} }
-53
View File
@@ -1,53 +0,0 @@
use common::prelude::Instruction;
use rustc_hash::FxHashMap;
#[derive(Debug)]
pub struct Cache {
addr: u32,
instruction_block: Option<[u8; 256]>,
instruction_lookup: FxHashMap<u32, Instruction>,
}
impl Cache {
#[must_use]
pub fn new() -> Self {
Self {
addr: 0,
instruction_block: None,
instruction_lookup: FxHashMap::default(),
}
}
pub fn lookup_value(&mut self, addr: u32) -> Option<u32> {
if addr < self.addr || addr >= self.addr + 256 || self.instruction_block.is_none()
{
return None;
}
Some(u32::from_be_bytes(
self.instruction_block.expect("this should not be none!")
[(addr - self.addr) as usize..(addr - self.addr + 4) as usize]
.try_into()
.expect("Failed to convert bytes to u32"),
))
}
pub const fn set(&mut self, addr: u32, block: &[u8; 256]) {
self.addr = addr - addr % 256;
self.instruction_block = Some(*block);
}
pub fn lookup_instruction(&mut self, instruction: u32) -> Option<Instruction> {
self.instruction_lookup.get(&instruction).copied()
}
pub fn insert(&mut self, value: u32, instruction: Instruction) {
self.instruction_lookup.insert(value, instruction);
}
}
impl Default for Cache {
fn default() -> Self {
Self::new()
}
}
+56 -41
View File
@@ -25,11 +25,9 @@ pub fn run_emulator(
let mut running = Running::Paused; let mut running = Running::Paused;
let mut step = 0; let mut step = 0;
let mut addr; let mut addr;
let mut history = Vec::<(u32, u32)>::with_capacity(32768); let mut history = Vec::<(u32, Instruction)>::new();
let size = 256; let size = 256;
let record_history = true;
state_tx state_tx
.send(StateUpdate::Running(Running::Paused)) .send(StateUpdate::Running(Running::Paused))
.expect("Failed to send initial state!"); .expect("Failed to send initial state!");
@@ -38,9 +36,7 @@ pub fn run_emulator(
let mut update = false; let mut update = false;
loop { loop {
let cmd = if step > 0 { let cmd = if running == Running::Running || step > 0 {
None
} else if running == Running::Running && step == 0 {
match cmd_rx.try_recv() { match cmd_rx.try_recv() {
Ok(cmd) => Some(cmd), Ok(cmd) => Some(cmd),
Err(mpsc::TryRecvError::Empty) => { Err(mpsc::TryRecvError::Empty) => {
@@ -56,15 +52,10 @@ pub fn run_emulator(
} }
}; };
if running == Running::Running && step == 0 {
step = 32768;
}
if let Some(cmd) = cmd { if let Some(cmd) = cmd {
match cmd { match cmd {
Command::Start => { Command::Start => {
running = Running::Running; running = Running::Running;
step = 32768;
// Update RPC with current state. TODO: Make this only occur on state // Update RPC with current state. TODO: Make this only occur on state
// changes. // changes.
@@ -80,11 +71,9 @@ pub fn run_emulator(
} }
Command::Stop => { Command::Stop => {
running = Running::Paused; running = Running::Paused;
step = 0;
} }
Command::Reset(x) => { Command::Reset(x) => {
running = Running::Paused; running = Running::Paused;
step = 0;
match x { match x {
0 => { 0 => {
@@ -106,13 +95,22 @@ pub fn run_emulator(
} }
Command::Step(x) => { Command::Step(x) => {
step = x; step = x;
running = Running::Paused;
} }
Command::Write(offset, data) => { Command::Write(offset, data) => {
update = true; update = true;
processor.memory.write_range(offset, data); processor
.memory
.write_range(offset, data)
.unwrap_or_else(|_| {
report_err(
state_tx,
"Failed to write memory range!",
&mut processor,
);
});
} }
#[expect(unused_assignments)]
Command::Interrupt(_interrupt) => { Command::Interrupt(_interrupt) => {
update = true; update = true;
@@ -121,7 +119,14 @@ pub fn run_emulator(
Command::MemRequest(new, size) if update => { Command::MemRequest(new, size) if update => {
addr = new; addr = new;
let _ = state_tx.send(StateUpdate::MemoryView( let _ = state_tx.send(StateUpdate::MemoryView(
processor.memory.read_range(addr, size), processor.memory.read_range(addr, size).unwrap_or_else(|_| {
report_err(
state_tx,
"Failed to read memory range!",
&mut processor,
);
Vec::new()
}),
)); ));
} }
Command::DisplayRequest if update => { Command::DisplayRequest if update => {
@@ -159,28 +164,34 @@ pub fn run_emulator(
let _ = state_tx.send(StateUpdate::Instructions(instruction_count)); let _ = state_tx.send(StateUpdate::Instructions(instruction_count));
} }
Command::WriteBlock(addr, block) => { Command::WriteBlock(addr, block) => {
processor.memory.write_range(addr, block.to_vec()); processor
.memory
.write_range(addr, block.to_vec())
.unwrap_or_else(|_| {
report_err(
state_tx,
"Failed to write memory block!",
&mut processor,
);
});
} }
_ => {} _ => {}
} }
} }
if running == Running::Running {
step += 1;
}
if step > 0 { if step > 0 {
step -= 1; step -= 1;
update = true; update = true;
running = Running::Paused;
// Execute one cycle. // Execute one cycle.
let instruction = match processor.cycle() { match processor.cycle() {
Ok(instruction) => instruction, Ok((addr, instruction)) => {
history.push((addr, instruction));
}
Err(why) => { Err(why) => {
let pcx = processor let pcx = processor.get(Register::Pcx);
.get(Register::Pcx)
.expect("PCX should never be invalid");
report_err( report_err(
state_tx, state_tx,
&format!( &format!(
@@ -188,22 +199,28 @@ pub fn run_emulator(
), ),
&mut processor, &mut processor,
); );
(pcx, Instruction::Nop) }
}
instruction_count += 1;
continue;
}
if running == Running::Running {
update = true;
// Execute one cycle.
let instruction = match processor.cycle() {
Ok(instruction) => instruction,
Err(why) => {
let pcx = processor.get(Register::Pcx);
eprintln!("Could not decode instruction at {pcx:x}. Reason: {why}");
continue;
} }
}; };
if record_history { history.push(instruction);
history.push(( if matches!(instruction.1, Instruction::Halt) {
instruction.0,
processor
.get(Register::Cir)
.expect("CIR should never be invalid"),
));
}
if matches!(instruction, (_, Instruction::Halt)) {
running = Running::Halted; running = Running::Halted;
step = 0;
} }
instruction_count += 1; instruction_count += 1;
@@ -212,8 +229,6 @@ pub fn run_emulator(
} }
fn report_err(state_tx: &Sender<StateUpdate>, why: &str, processor: &mut Processor) { fn report_err(state_tx: &Sender<StateUpdate>, why: &str, processor: &mut Processor) {
processor processor.begin_interrupt(Interrupt::HardFault);
.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.");
let _ = state_tx.send(StateUpdate::Error(why.to_string())); let _ = state_tx.send(StateUpdate::Error(why.to_string()));
} }
+86 -73
View File
@@ -1,42 +1,52 @@
use rustc_hash::FxHashMap; use std::collections::HashMap;
use crate::emulator::system::model::ProcessorError; use crate::emulator::system::model::ProcessorError;
pub trait MemoryUnit: Send + Sync { pub trait MemoryUnit: Send + Sync {
fn reset(&mut self); fn reset(&mut self);
fn read_byte(&mut self, addr: u32) -> u8; fn read_byte(&mut self, addr: u32) -> Result<u8, ProcessorError>;
fn write_byte(&mut self, addr: u32, value: u8); fn write_byte(&mut self, addr: u32, value: u8) -> Result<(), ProcessorError>;
fn read_word(&mut self, addr: u32) -> Result<u32, ProcessorError>; fn read_word(&mut self, addr: u32) -> Result<u32, ProcessorError>;
fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError>; fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError>;
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8> { fn read_range(&mut self, addr: u32, size: u32) -> Result<Vec<u8>, ProcessorError> {
let mut data = Vec::with_capacity(size as usize); let mut data = Vec::with_capacity(size as usize);
for i in 0..size { for i in 0..size {
data.push(self.read_byte(addr + i)); data.push(self.read_byte(addr + i)?);
} }
data Ok(data)
} }
fn write_range(&mut self, addr: u32, value: Vec<u8>) { fn write_range(&mut self, addr: u32, value: Vec<u8>) -> Result<(), ProcessorError> {
for (i, byte) in value.into_iter().enumerate() { for (i, byte) in value.into_iter().enumerate() {
self.write_byte(addr + i as u32, byte); self.write_byte(addr + i as u32, byte)?;
} }
Ok(())
} }
fn read_block(&mut self, addr: u32) -> &[u8; 256]; fn read_block(&mut self, addr: u32) -> Result<[u8; 256], ProcessorError> {
let mut data = [0; 256];
fn write_block(&mut self, addr: u32, data: &[u8; 256]) { for (i, byte) in data.iter_mut().enumerate() {
for (i, byte) in data.iter().enumerate() { *byte = self.read_byte(addr + i as u32)?;
self.write_byte(addr + i as u32, *byte);
} }
Ok(data)
}
fn write_block(&mut self, addr: u32, data: [u8; 256]) -> Result<(), ProcessorError> {
for (i, byte) in data.iter().enumerate() {
self.write_byte(addr + i as u32, *byte)?;
}
Ok(())
} }
} }
pub struct MainStore { pub struct MainStore {
pub data: FxHashMap<u32, Block>, pub data: HashMap<u32, Block>,
} }
pub type Block = [u8; 256]; pub struct Block {
data: [u8; 256],
}
impl Default for MainStore { impl Default for MainStore {
fn default() -> Self { fn default() -> Self {
@@ -48,110 +58,113 @@ impl MainStore {
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
data: FxHashMap::default(), data: HashMap::new(),
} }
} }
#[inline]
const fn segment_addr(addr: u32) -> (u32, u8) { const fn segment_addr(addr: u32) -> (u32, u8) {
(addr / 256, (addr % 256) as u8) (addr / 256, (addr % 256) as u8)
} }
#[inline]
fn mut_block(&mut self, addr: u32) -> &mut Block { fn mut_block(&mut self, addr: u32) -> &mut Block {
self.data.entry(addr).or_insert([0; 256]) self.data
.entry(addr)
.or_insert_with(|| Block { data: [0; 256] });
self.data.get_mut(&addr).map_or_else(
|| panic!("Could not fetch block with address {addr:x?}"),
|block| block,
)
} }
#[inline]
fn block(&mut self, addr: u32) -> &Block { fn block(&mut self, addr: u32) -> &Block {
self.data.entry(addr).or_insert([0; 256]) self.data
.entry(addr)
.or_insert_with(|| Block { data: [0; 256] });
self.data.get(&addr).map_or_else(
|| panic!("Could not fetch block with address {addr:x?}"),
|block| block,
)
} }
} }
impl MemoryUnit for MainStore { impl MemoryUnit for MainStore {
#[inline]
fn reset(&mut self) { fn reset(&mut self) {
self.data.clear(); self.data.clear();
} }
#[inline] fn read_byte(&mut self, addr: u32) -> Result<u8, ProcessorError> {
fn read_byte(&mut self, addr: u32) -> u8 {
let (block_addr, offset) = Self::segment_addr(addr); let (block_addr, offset) = Self::segment_addr(addr);
let block = self.block(block_addr); let block = self.block(block_addr);
block[offset as usize] Ok(block.data[offset as usize])
} }
#[inline]
fn read_word(&mut self, addr: u32) -> Result<u32, ProcessorError> { fn read_word(&mut self, addr: u32) -> Result<u32, ProcessorError> {
if !addr.is_multiple_of(4) { if addr % 4 != 0 {
return Err(ProcessorError::BadMemoryAccess(addr)); return Err(ProcessorError::BadMemoryAccess(addr));
} }
let (block_addr, offset) = Self::segment_addr(addr); let (block_addr, offset) = Self::segment_addr(addr);
let offset = offset as usize; let block = self.mut_block(block_addr);
let block = self.block(block_addr); let mut bytes = [0; 4];
Ok(u32::from_be_bytes( bytes[0] = block.data[offset as usize];
block[offset..=offset + 3] bytes[1] = block.data[(offset + 1) as usize];
.try_into() bytes[2] = block.data[(offset + 2) as usize];
.expect("Failed to read word!"), bytes[3] = block.data[(offset + 3) as usize];
)) Ok(u32::from_be_bytes(bytes))
} }
#[inline] fn read_range(&mut self, addr: u32, size: u32) -> Result<Vec<u8>, ProcessorError> {
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8> {
let mut data = Vec::with_capacity(size as usize); let mut data = Vec::with_capacity(size as usize);
for i in 0..size { for i in 0..size {
data.push(self.read_byte(addr + i)); data.push(self.read_byte(addr + i)?);
} }
data Ok(data)
} }
#[inline] fn write_byte(&mut self, addr: u32, value: u8) -> Result<(), ProcessorError> {
fn write_byte(&mut self, addr: u32, value: u8) {
let (block_addr, offset) = Self::segment_addr(addr); let (block_addr, offset) = Self::segment_addr(addr);
let block = self.mut_block(block_addr); let block = self.mut_block(block_addr);
block[offset as usize] = value; block.data[offset as usize] = value;
}
#[inline]
fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError> {
if !addr.is_multiple_of(4) {
return Err(ProcessorError::BadMemoryAccess(addr));
}
let (block_addr, offset) = Self::segment_addr(addr);
let block = self.mut_block(block_addr);
block[offset as usize..=(offset + 3) as usize]
.copy_from_slice(&value.to_be_bytes());
Ok(()) Ok(())
} }
#[inline] fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError> {
fn write_range(&mut self, addr: u32, value: Vec<u8>) { if addr % 4 != 0 {
let mut current_block_addr = addr / 256; return Err(ProcessorError::BadMemoryAccess(addr));
let mut current_block = self.mut_block(current_block_addr);
let mut offset = addr % 256;
for byte in value {
current_block[offset as usize] = byte;
offset += 1;
if offset >= 256 {
offset = 0;
current_block_addr += 1;
current_block = self.mut_block(current_block_addr);
}
} }
let (block_addr, offset) = Self::segment_addr(addr);
let block = self.mut_block(block_addr);
block.data[offset as usize] = (value >> 24) as u8;
block.data[(offset + 1) as usize] = (value >> 16) as u8;
block.data[(offset + 2) as usize] = (value >> 8) as u8;
block.data[(offset + 3) as usize] = value as u8;
Ok(())
} }
#[inline] fn write_range(&mut self, addr: u32, value: Vec<u8>) -> Result<(), ProcessorError> {
fn read_block(&mut self, addr: u32) -> &[u8; 256] { for (i, byte) in value.into_iter().enumerate() {
let (block_addr, _) = Self::segment_addr(addr); let (block_addr, offset) = Self::segment_addr(addr + i as u32);
self.block(block_addr) let block = self.mut_block(block_addr);
block.data[offset as usize] = byte;
}
Ok(())
} }
#[inline] fn read_block(&mut self, addr: u32) -> Result<[u8; 256], ProcessorError> {
fn write_block(&mut self, addr: u32, data: &[u8; 256]) {
let (block_addr, _) = Self::segment_addr(addr); let (block_addr, _) = Self::segment_addr(addr);
let _ = self.data.insert(block_addr, *data); let block = self.block(block_addr);
Ok(block.data)
}
fn write_block(&mut self, addr: u32, data: [u8; 256]) -> Result<(), ProcessorError> {
let (block_addr, _) = Self::segment_addr(addr);
let block = self.mut_block(block_addr);
block.data = data;
Ok(())
} }
} }
-1
View File
@@ -1,4 +1,3 @@
pub mod cache;
pub mod emulator; pub mod emulator;
pub mod memory; pub mod memory;
pub mod model; pub mod model;
+11 -10
View File
@@ -78,7 +78,7 @@ pub struct State {
pub error_log: Vec<String>, pub error_log: Vec<String>,
pub instruction_history: Vec<(u32, u32)>, pub instruction_history: Vec<(u32, Instruction)>,
} }
impl State { impl State {
@@ -154,7 +154,7 @@ pub enum StateUpdate {
MemoryView(Vec<u8>), MemoryView(Vec<u8>),
DisplayView(Vec<u8>), DisplayView(Vec<u8>),
Error(String), Error(String),
InstructionHistory(Vec<(u32, u32)>), InstructionHistory(Vec<(u32, Instruction)>),
} }
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
@@ -257,8 +257,8 @@ impl RegFile {
self.pcx = 0; self.pcx = 0;
} }
pub const fn reg(&mut self, reg: Register) -> Result<&mut u32, ProcessorError> { pub fn reg(&mut self, reg: Register) -> &mut u32 {
Ok(match reg { match reg {
Register::Rg0 => &mut self.rg0, Register::Rg0 => &mut self.rg0,
Register::Rg1 => &mut self.rg1, Register::Rg1 => &mut self.rg1,
Register::Rg2 => &mut self.rg2, Register::Rg2 => &mut self.rg2,
@@ -286,12 +286,13 @@ impl RegFile {
Register::Sts => &mut self.sts, Register::Sts => &mut self.sts,
Register::Cir => &mut self.cir, Register::Cir => &mut self.cir,
Register::Pcx => &mut self.pcx, Register::Pcx => &mut self.pcx,
_ => return Err(ProcessorError::InvalidRegister(Register::Null as u8)), _ => panic!("Invalid register."),
}) }
} }
pub const fn get(&self, reg: Register) -> Result<u32, ProcessorError> { #[must_use]
Ok(match reg { pub fn get(&self, reg: Register) -> u32 {
match reg {
Register::Rg0 => self.rg0, Register::Rg0 => self.rg0,
Register::Rg1 => self.rg1, Register::Rg1 => self.rg1,
Register::Rg2 => self.rg2, Register::Rg2 => self.rg2,
@@ -320,7 +321,7 @@ impl RegFile {
Register::Cir => self.cir, Register::Cir => self.cir,
Register::Pcx => self.pcx, Register::Pcx => self.pcx,
Register::Zero => 0, Register::Zero => 0,
_ => return Err(ProcessorError::InvalidRegister(Register::Null as u8)), _ => panic!("Invalid register."),
}) }
} }
} }
+101 -139
View File
@@ -4,7 +4,6 @@ use std::{
}; };
use crate::emulator::system::{ use crate::emulator::system::{
cache::Cache,
memory::MemoryUnit, memory::MemoryUnit,
model::{IODevice, ProcessorError, RegFile}, model::{IODevice, ProcessorError, RegFile},
}; };
@@ -17,8 +16,12 @@ pub struct Processor {
pub halted: bool, pub halted: bool,
pub io_devices: Vec<Arc<dyn IODevice>>, pub io_devices: Vec<Arc<dyn IODevice>>,
pub void: u32, pub dustbin: u32,
pub cache: Cache, }
#[expect(dead_code)]
fn log(message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}");
} }
impl Processor { impl Processor {
@@ -29,8 +32,7 @@ impl Processor {
registers: RegFile::default(), registers: RegFile::default(),
halted: false, halted: false,
io_devices, io_devices,
void: 0, dustbin: 0,
cache: Cache::new(),
} }
} }
@@ -48,56 +50,43 @@ impl Processor {
self.halted = false; self.halted = false;
// Get value from PCX. // Get value from PCX.
let addr = self.fetch()?; let addr = self.fetch();
// Increment PCX. // Increment PCX.
self.advance()?; self.advance();
// Set MAR to the previous value of PCX. // 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)?;
let encoded = if let Some(val) = self.cache.lookup_value(addr) {
val
} else {
let block = self.memory.read_block(addr);
self.cache.set(addr, block);
self.cache
.lookup_value(addr)
.expect("Failed to lookup value!")
};
// Set CIR to the value of RAM[MAR]. // Set CIR to the value of RAM[MAR].
*self.reg(Register::Cir)? = encoded; *self.reg(Register::Mar) = val;
let decoded = if let Some(val) = self.cache.lookup_instruction(addr) { // Decode and execute the instruction.
val let instruction = Instruction::decode(val)
} else { .map_err(|_| ProcessorError::InvalidInstruction(val))?;
let decoded = Instruction::decode(encoded)
.map_err(|_| ProcessorError::InvalidInstruction(encoded))?;
self.cache.insert(addr, decoded);
decoded
};
decoded.execute(self)?; instruction.execute(self)?;
Ok((addr, decoded)) Ok((addr, instruction))
} }
const fn fetch(&self) -> Result<u32, ProcessorError> { fn fetch(&self) -> u32 {
self.get(Register::Pcx) 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) 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 { match reg {
Register::Zero => Ok(&mut self.void), Register::Zero => &mut self.dustbin,
_ => self.registers.reg(reg), _ => self.registers.reg(reg),
} }
} }
pub fn display(&mut self) -> Result<Vec<u8>, ProcessorError> { pub fn display(&mut self) -> Result<Vec<u8>, ProcessorError> {
Ok(self.memory.read_range(0x20000, 2000)) self.memory.read_range(0x20000, 2000)
} }
pub fn cmp(&mut self, a: u32, b: u32) { pub fn cmp(&mut self, a: u32, b: u32) {
@@ -109,77 +98,44 @@ impl Processor {
// functions to set new state // functions to set new state
fn set_flag(&mut self, flag: Flag, value: bool) { fn set_flag(&mut self, flag: Flag, value: bool) {
if value { if value {
*self *self.reg(Register::Sts) |= flag as u32;
.reg(Register::Sts)
.expect("STS should never be invalid") |= flag as u32;
} else { } else {
*self *self.reg(Register::Sts) &= !(flag as u32);
.reg(Register::Sts)
.expect("STS should never be invalid") &= !(flag as u32);
} }
} }
fn get_flag(&self, flag: Flag) -> Result<bool, ProcessorError> { fn get_flag(&self, flag: Flag) -> bool {
Ok(self.get(Register::Sts)? & (flag as u32) != 0) self.get(Register::Sts) & (flag as u32) != 0
} }
fn advance(&mut self) -> Result<(), ProcessorError> { fn advance(&mut self) {
// increment PCX // increment PCX
*self.reg(Register::Pcx)? += 4; *self.reg(Register::Pcx) += 4;
Ok(())
} }
fn jump(&mut self, reg: Register, offset: u16) -> Result<(), ProcessorError> { fn jump(&mut self, reg: Register, offset: u16) {
*self.reg(Register::Pcx)? = self.get(reg)? + u32::from(offset); *self.reg(Register::Pcx) = self.get(reg) + u32::from(offset);
Ok(())
} }
pub fn begin_interrupt( pub fn begin_interrupt(&mut self, _int: Interrupt) {
&mut self, // first we get the address of the interrupt descriptor table.
interrupt: Interrupt, todo!();
) -> 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)
} }
// TODO: remove this once implemented // TODO: remove this once implemented
#[allow(clippy::needless_pass_by_ref_mut)] #[allow(clippy::needless_pass_by_ref_mut)]
fn end_interrupt(&mut self) -> Result<(), ProcessorError> { fn end_interrupt(&mut self) {
let ret = self.pop()?; todo!();
*self.reg(Register::Ret)? = ret;
*self.reg(Register::Pcx)? = ret;
Ok(())
} }
pub fn get_stack(&mut self, n: u32) -> Result<Vec<u8>, ProcessorError> { 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; let size = n * 4;
// returns the stack // returns the stack
Ok(self.memory.read_range( self.memory.read_range(
max(addr, 0), // ensures that we cannot read from a negative address max(addr, 0), // ensures that we cannot read from a negative address
min(size, addr), // ensures we don't read above the top of the stack min(size, addr), // ensures we don't read above the top of the stack
)) )
} }
} }
@@ -208,30 +164,30 @@ impl Executable for Instruction {
// No operation - a blank line. // No operation - a blank line.
// Copies from SrcReg to a.drReg. // Copies from SrcReg to a.drReg.
Self::Mov(a) => { 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 // Copies from SrcReg to a.drReg, sign extending the value to take up a full
// word. // word.
Self::MovSigned(a) => { 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 // Loads a byte from memory address (base + offset) into a.drReg. The
// effective address must be byte-aligned. // effective address must be byte-aligned.
Self::LoadByte(a) => { Self::LoadByte(a) => {
*cpu.reg(a.r2)? = u32::from( *cpu.reg(a.r2) = u32::from(
cpu.memory 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 // Loads a sign-extended byte from memory address (base + offset) into
// a.drReg. The effective address must be byte-aligned. // a.drReg. The effective address must be byte-aligned.
Self::LoadByteSigned(a) => { Self::LoadByteSigned(a) => {
*cpu.reg(a.r2)? = sign_extend(u32::from( *cpu.reg(a.r2) = sign_extend(u32::from(
cpu.memory cpu.memory
.read_byte(cpu.get(a.r1)? + u32::from(a.immediate)), .read_byte(cpu.get(a.r1) + u32::from(a.immediate))?,
)); ));
} }
@@ -240,18 +196,18 @@ impl Executable for Instruction {
Self::LoadHalfword(a) => { Self::LoadHalfword(a) => {
// we read an entire word, then right shift so we only get the first half // we read an entire word, then right shift so we only get the first half
// of the word // of the word
*cpu.reg(a.r2)? = cpu *cpu.reg(a.r2) = cpu
.memory .memory
.read_word(cpu.get(a.r1)? + u32::from(a.immediate))? .read_word(cpu.get(a.r1) + u32::from(a.immediate))?
>> 16; >> 16;
} }
// Loads a sign-extended half-word from memory address (base + offset) into // Loads a sign-extended half-word from memory address (base + offset) into
// a.drReg. The effective address must be 2-byte-aligned. // a.drReg. The effective address must be 2-byte-aligned.
Self::LoadHalfwordSigned(a) => { Self::LoadHalfwordSigned(a) => {
*cpu.reg(a.r2)? = sign_extend( *cpu.reg(a.r2) = sign_extend(
cpu.memory cpu.memory
.read_word(cpu.get(a.r1)? + u32::from(a.immediate))? .read_word(cpu.get(a.r1) + u32::from(a.immediate))?
>> 16, >> 16,
); );
} }
@@ -259,161 +215,167 @@ impl Executable for Instruction {
// Loads a word from memory address (base + offset) into a.drReg. The // Loads a word from memory address (base + offset) into a.drReg. The
// effective address must be 4-byte-aligned. // effective address must be 4-byte-aligned.
Self::LoadWord(a) => { Self::LoadWord(a) => {
*cpu.reg(a.r2)? = cpu *cpu.reg(a.r2) = cpu
.memory .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 // Stores a byte from SrcReg in memory address (base + offset) The effective
// address must be byte-aligned. // address must be byte-aligned.
Self::StoreByte(a) => { Self::StoreByte(a) => {
cpu.memory.write_byte( cpu.memory.write_byte(
cpu.get(a.r2)? + u32::from(a.immediate), cpu.get(a.r2) + u32::from(a.immediate),
cpu.get(a.r1)? as u8, cpu.get(a.r1) as u8,
); )?;
} }
// Stores a half-word from SrcReg in memory address (base + offset) The // Stores a half-word from SrcReg in memory address (base + offset) The
// effective address must be 2-byte-aligned. // effective address must be 2-byte-aligned.
Self::StoreHalfword(a) => { Self::StoreHalfword(a) => {
// split the value into bytes and then write two bytes // 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 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 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 // Stores a word from SrcReg in memory address (base + offset) The effective
// address must be 4-byte-aligned. // address must be 4-byte-aligned.
Self::StoreWord(a) => { Self::StoreWord(a) => {
cpu.memory.write_word( cpu.memory
cpu.get(a.r2)? + u32::from(a.immediate), .write_word(cpu.get(a.r2) + u32::from(a.immediate), cpu.get(a.r1))?;
cpu.get(a.r1)?,
)?;
} }
// Loads a 16-bit literal value into reg, setting the bottom 16 bits of the // Loads a 16-bit literal value into reg, setting the bottom 16 bits of the
// word. To populate the upper 16 bits, see LUI. // word. To populate the upper 16 bits, see LUI.
Self::LoadLowerImmediate(a) => { 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. // Loads a 16-bit literal value into reg, setting the top 16 bits of the word.
// To populate the lower 16 bits, see LLI. // To populate the lower 16 bits, see LLI.
Self::LoadUpperImmediate(a) => { Self::LoadUpperImmediate(a) => {
*cpu.reg(a.r1)? = *cpu.reg(a.r1) =
(cpu.get(a.r1)? & 0x0000_FFFF) | (u32::from(a.immediate) << 16); (cpu.get(a.r1) & 0x0000_FFFF) | (u32::from(a.immediate) << 16);
} }
// Unconditionally jumps to the calculated address or direct address // 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. // Jumps to the calculated address or direct address if equal flag set.
Self::JumpEq(a) => { Self::JumpEq(a) => {
if cpu.get_flag(Flag::Equal)? { if cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate)?; cpu.jump(a.r1, a.immediate);
} }
} }
// Jumps to the calculated address or direct address if equal flag not set. // Jumps to the calculated address or direct address if equal flag not set.
Self::JumpNeq(a) => { Self::JumpNeq(a) => {
if !cpu.get_flag(Flag::Equal)? { if !cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate)?; cpu.jump(a.r1, a.immediate);
} }
} }
// Jumps to the calculated address or direct address if greater than flag set. // Jumps to the calculated address or direct address if greater than flag set.
Self::JumpGt(a) => { Self::JumpGt(a) => {
if cpu.get_flag(Flag::GreaterThan)? { if cpu.get_flag(Flag::GreaterThan) {
cpu.jump(a.r1, a.immediate)?; cpu.jump(a.r1, a.immediate);
} }
} }
// Jumps to the calculated address or direct address if greater than flag or // Jumps to the calculated address or direct address if greater than flag or
// equal flag set. // equal flag set.
Self::JumpGe(a) => { Self::JumpGe(a) => {
if cpu.get_flag(Flag::GreaterThan)? || cpu.get_flag(Flag::Equal)? { if cpu.get_flag(Flag::GreaterThan) || cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate)?; cpu.jump(a.r1, a.immediate);
} }
} }
// Jumps to the calculated address or direct address if less than flag set. // Jumps to the calculated address or direct address if less than flag set.
Self::JumpLt(a) => { Self::JumpLt(a) => {
if cpu.get_flag(Flag::LessThan)? { if cpu.get_flag(Flag::LessThan) {
cpu.jump(a.r1, a.immediate)?; cpu.jump(a.r1, a.immediate);
} }
} }
// Jumps to the calculated address or direct address if less than flag or // Jumps to the calculated address or direct address if less than flag or
// equal flag set. // equal flag set.
Self::JumpLe(a) => { Self::JumpLe(a) => {
if cpu.get_flag(Flag::LessThan)? || cpu.get_flag(Flag::Equal)? { if cpu.get_flag(Flag::LessThan) || cpu.get_flag(Flag::Equal) {
cpu.jump(a.r1, a.immediate)?; cpu.jump(a.r1, a.immediate);
} }
} }
// Increments the value in the given register // 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 // 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 // Left shifts the value in Reg by the given amount (either a register, or a
// literal value) // literal value)
Self::ShiftLeft(a) => { Self::ShiftLeft(a) => {
*cpu.reg(a.dr)? = shl(cpu.get(a.sr1)?, a.shamt + cpu.get(a.sr2)? as u8); 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 // Right shifts the value in Reg by the given amount (either a register, or a
// literal value). // literal value).
Self::ShiftRight(a) => { Self::ShiftRight(a) => {
*cpu.reg(a.dr)? = shr(cpu.get(a.sr1)?, a.shamt + cpu.get(a.sr2)? as u8); 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 // Adds the value of Src2 to Src1 and writes the result to a.dr
Self::Add(a) => { 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 // Subtracts the value of Src2 from Src1 and writes the result to a.dr
Self::Sub(a) => { 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) => { 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) => { 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 // 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 // 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 // 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 // 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 // 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 // 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 // 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 // Compares the value of Reg1 to the value in Reg2. The results of the
// comparisons are set in the Status register. // comparisons are set in the Status register.
Self::Compare(a) => { 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. // Initiates an interrupt with the given 8 bit interrupt code.
@@ -421,12 +383,12 @@ impl Executable for Instruction {
// - The return address is saved to the RET register. // - The return address is saved to the RET register.
// - The stack base ptr is set to the kernel stack. // - The stack base ptr is set to the kernel stack.
Self::Interrupt(interrupt_code) => { Self::Interrupt(interrupt_code) => {
cpu.begin_interrupt(interrupt_code)?; cpu.begin_interrupt(interrupt_code);
} }
// Returns from an interrupt, // Returns from an interrupt,
Self::IntReturn => { Self::IntReturn => {
cpu.end_interrupt()?; cpu.end_interrupt();
} }
// Halts the processor. // Halts the processor.
+106 -202
View File
@@ -18,27 +18,19 @@ fn test_nop_instruction() {
); );
assert_eq!( assert_eq!(
cpu.registers cpu.registers.get(Register::Rg0),
.get(Register::Rg0) initial_state.get(Register::Rg0)
.expect("Failed to get register Rg0"),
initial_state
.get(Register::Rg0)
.expect("Failed to get register Rg0")
); );
assert_eq!( assert_eq!(
cpu.registers cpu.registers.get(Register::Acc),
.get(Register::Acc) initial_state.get(Register::Acc)
.expect("Failed to get register Acc"),
initial_state
.get(Register::Acc)
.expect("Failed to get register Acc")
); );
} }
#[test] #[test]
fn test_mov_instruction() { fn test_mov_instruction() {
let mut cpu = create_test_processor(); 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( let mov_instr = Instruction::Mov(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -50,16 +42,13 @@ fn test_mov_instruction() {
mov_instr.execute(&mut cpu).expect( mov_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg2), 0x1234_5678);
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0x1234_5678
);
} }
#[test] #[test]
fn test_mov_signed_instruction() { fn test_mov_signed_instruction() {
let mut cpu = create_test_processor(); 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( let mov_signed_instr = Instruction::MovSigned(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -71,18 +60,17 @@ fn test_mov_signed_instruction() {
mov_signed_instr.execute(&mut cpu).expect( mov_signed_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg2), 0xFFFF_FFFF);
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0xFFFF_FFFF
);
} }
#[test] #[test]
fn test_load_byte_instruction() { fn test_load_byte_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
let addr = 0x100; let addr = 0x100;
cpu.memory.write_byte(addr, 0xAB); cpu.memory
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr - 4; .write_byte(addr, 0xAB)
.expect("Failed to write byte to memory");
*cpu.reg(Register::Rg1) = addr - 4;
let load_byte_instr = Instruction::LoadByte(ITypeArgs::new( let load_byte_instr = Instruction::LoadByte(ITypeArgs::new(
4, 4,
@@ -93,18 +81,17 @@ fn test_load_byte_instruction() {
load_byte_instr.execute(&mut cpu).expect( load_byte_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg2), 0x0000_00AB);
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0x0000_00AB
);
} }
#[test] #[test]
fn test_load_byte_signed_instruction() { fn test_load_byte_signed_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
let addr = 0x100; let addr = 0x100;
cpu.memory.write_byte(addr, 0xFF); cpu.memory
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr; .write_byte(addr, 0xFF)
.expect("Failed to write byte to memory");
*cpu.reg(Register::Rg1) = addr;
let load_byte_signed_instr = Instruction::LoadByteSigned(ITypeArgs::new( let load_byte_signed_instr = Instruction::LoadByteSigned(ITypeArgs::new(
0, 0,
@@ -115,10 +102,7 @@ fn test_load_byte_signed_instruction() {
load_byte_signed_instr.execute(&mut cpu).expect( load_byte_signed_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg2), 0xFFFF_FFFF);
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0xFFFF_FFFF
);
} }
#[test] #[test]
@@ -128,7 +112,7 @@ fn test_load_halfword_instruction() {
cpu.memory cpu.memory
.write_word(addr, 0x1234_5678) .write_word(addr, 0x1234_5678)
.expect("Failed to write word to memory"); .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( let load_halfword_instr = Instruction::LoadHalfword(ITypeArgs::new(
0, 0,
@@ -139,10 +123,7 @@ fn test_load_halfword_instruction() {
load_halfword_instr.execute(&mut cpu).expect( load_halfword_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg2), 0x0000_1234);
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0x0000_1234
);
} }
#[test] #[test]
@@ -152,7 +133,7 @@ fn test_load_word_instruction() {
cpu.memory cpu.memory
.write_word(addr, 0x1234_5678) .write_word(addr, 0x1234_5678)
.expect("Failed to write word to memory"); .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( let load_word_instr = Instruction::LoadWord(ITypeArgs::new(
0, 0,
@@ -163,18 +144,15 @@ fn test_load_word_instruction() {
load_word_instr.execute(&mut cpu).expect( load_word_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg2), 0x1234_5678);
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0x1234_5678
);
} }
#[test] #[test]
fn test_store_byte_instruction() { fn test_store_byte_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
let addr = 0x100; let addr = 0x100;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr; *cpu.reg(Register::Rg1) = addr;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0xAB; *cpu.reg(Register::Rg2) = 0xAB;
let store_byte_instr = Instruction::StoreByte(ITypeArgs::new( let store_byte_instr = Instruction::StoreByte(ITypeArgs::new(
0, 0,
@@ -185,15 +163,15 @@ fn test_store_byte_instruction() {
store_byte_instr.execute(&mut cpu).expect( store_byte_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!(cpu.memory.read_byte(addr), 0xAB); assert_eq!(cpu.memory.read_byte(addr).expect("Emulator was slain by losing the game while attempting to execute instruction"), 0xAB);
} }
#[test] #[test]
fn test_store_word_instruction() { fn test_store_word_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
let addr = 0x100; let addr = 0x100;
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr; *cpu.reg(Register::Rg1) = addr;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0x1234_5678; *cpu.reg(Register::Rg2) = 0x1234_5678;
let store_word_instr = Instruction::StoreWord(ITypeArgs::new( let store_word_instr = Instruction::StoreWord(ITypeArgs::new(
0, 0,
@@ -210,8 +188,8 @@ fn test_store_word_instruction() {
#[test] #[test]
fn test_add_instruction() { fn test_add_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 15; *cpu.reg(Register::Rg1) = 15;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 25; *cpu.reg(Register::Rg2) = 25;
let add_instr = Instruction::Add(RTypeArgs::new( let add_instr = Instruction::Add(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -223,17 +201,14 @@ fn test_add_instruction() {
add_instr.execute(&mut cpu).expect( add_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg3), 40);
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
40
);
} }
#[test] #[test]
fn test_sub_instruction() { fn test_sub_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 50; *cpu.reg(Register::Rg1) = 50;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 20; *cpu.reg(Register::Rg2) = 20;
let sub_instr = Instruction::Sub(RTypeArgs::new( let sub_instr = Instruction::Sub(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -245,17 +220,14 @@ fn test_sub_instruction() {
sub_instr.execute(&mut cpu).expect( sub_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg3), 30);
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
30
);
} }
#[test] #[test]
fn test_and_instruction() { fn test_and_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100; *cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010; *cpu.reg(Register::Rg2) = 0b1010;
let and_instr = Instruction::And(RTypeArgs::new( let and_instr = Instruction::And(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -267,17 +239,14 @@ fn test_and_instruction() {
and_instr.execute(&mut cpu).expect( and_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg3), 0b1000);
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
0b1000
);
} }
#[test] #[test]
fn test_or_instruction() { fn test_or_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100; *cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010; *cpu.reg(Register::Rg2) = 0b1010;
let or_instr = Instruction::Or(RTypeArgs::new( let or_instr = Instruction::Or(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -289,17 +258,14 @@ fn test_or_instruction() {
or_instr.execute(&mut cpu).expect( or_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg3), 0b1110);
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
0b1110
);
} }
#[test] #[test]
fn test_xor_instruction() { fn test_xor_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100; *cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010; *cpu.reg(Register::Rg2) = 0b1010;
let xor_instr = Instruction::Xor(RTypeArgs::new( let xor_instr = Instruction::Xor(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -311,16 +277,13 @@ fn test_xor_instruction() {
xor_instr.execute(&mut cpu).expect( xor_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg3), 0b0110);
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
0b0110
);
} }
#[test] #[test]
fn test_not_instruction() { fn test_not_instruction() {
let mut cpu = create_test_processor(); 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( let not_instr = Instruction::Not(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -332,17 +295,14 @@ fn test_not_instruction() {
not_instr.execute(&mut cpu).expect( not_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg2), 0xF0F0_F0F0);
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0xF0F0_F0F0
);
} }
#[test] #[test]
fn test_compare_equal() { fn test_compare_equal() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 42; *cpu.reg(Register::Rg1) = 42;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 42; *cpu.reg(Register::Rg2) = 42;
let cmp_instr = Instruction::Compare(RTypeArgs::new( let cmp_instr = Instruction::Compare(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -355,22 +315,16 @@ fn test_compare_equal() {
"Emulator was slain by losing the game while attempting to execute instruction", "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::Equal));
assert!( assert!(!cpu.get_flag(Flag::GreaterThan));
!cpu.get_flag(Flag::GreaterThan) assert!(!cpu.get_flag(Flag::LessThan));
.expect("Failed to get flag GreaterThan")
);
assert!(
!cpu.get_flag(Flag::LessThan)
.expect("Failed to get flag LessThan")
);
} }
#[test] #[test]
fn test_compare_greater_than() { fn test_compare_greater_than() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 50; *cpu.reg(Register::Rg1) = 50;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 30; *cpu.reg(Register::Rg2) = 30;
let cmp_instr = Instruction::Compare(RTypeArgs::new( let cmp_instr = Instruction::Compare(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -383,22 +337,16 @@ fn test_compare_greater_than() {
"Emulator was slain by losing the game while attempting to execute instruction", "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::Equal));
assert!( assert!(cpu.get_flag(Flag::GreaterThan));
cpu.get_flag(Flag::GreaterThan) assert!(!cpu.get_flag(Flag::LessThan));
.expect("Failed to get flag GreaterThan")
);
assert!(
!cpu.get_flag(Flag::LessThan)
.expect("Failed to get flag LessThan")
);
} }
#[test] #[test]
fn test_compare_less_than() { fn test_compare_less_than() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 20; *cpu.reg(Register::Rg1) = 20;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 30; *cpu.reg(Register::Rg2) = 30;
let cmp_instr = Instruction::Compare(RTypeArgs::new( let cmp_instr = Instruction::Compare(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -411,21 +359,15 @@ fn test_compare_less_than() {
"Emulator was slain by losing the game while attempting to execute instruction", "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::Equal));
assert!( assert!(!cpu.get_flag(Flag::GreaterThan));
!cpu.get_flag(Flag::GreaterThan) assert!(cpu.get_flag(Flag::LessThan));
.expect("Failed to get flag GreaterThan")
);
assert!(
cpu.get_flag(Flag::LessThan)
.expect("Failed to get flag LessThan")
);
} }
#[test] #[test]
fn test_increment_instruction() { fn test_increment_instruction() {
let mut cpu = create_test_processor(); 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 = let inc_instr =
Instruction::Increment(RTypeArgs::new(Some(Register::Rg1), None, None, None)); Instruction::Increment(RTypeArgs::new(Some(Register::Rg1), None, None, None));
@@ -433,16 +375,13 @@ fn test_increment_instruction() {
inc_instr.execute(&mut cpu).expect( inc_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg1), 43);
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
43
);
} }
#[test] #[test]
fn test_decrement_instruction() { fn test_decrement_instruction() {
let mut cpu = create_test_processor(); 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 = let dec_instr =
Instruction::Decrement(RTypeArgs::new(Some(Register::Rg1), None, None, None)); Instruction::Decrement(RTypeArgs::new(Some(Register::Rg1), None, None, None));
@@ -450,71 +389,63 @@ fn test_decrement_instruction() {
dec_instr.execute(&mut cpu).expect( dec_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg1), 41);
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
41
);
} }
#[test] #[test]
fn test_shift_left_with_shamt() { fn test_shift_left_with_shamt() {
let mut cpu = create_test_processor(); 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( let shl_instr = Instruction::ShiftLeft(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
Some(Register::Zero), Some(Register::Zero),
Some(Register::Rg1), None,
Some(2), Some(2),
)); ));
shl_instr.execute(&mut cpu).expect( shl_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg1), 0b10_1000);
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
0b10_1000
);
} }
#[test] #[test]
fn test_shift_right_with_shamt() { fn test_shift_right_with_shamt() {
let mut cpu = create_test_processor(); 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( let shr_instr = Instruction::ShiftRight(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
Some(Register::Zero), Some(Register::Zero),
Some(Register::Rg1), None,
Some(2), Some(2),
)); ));
shr_instr.execute(&mut cpu).expect( shr_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg1), 0b1010);
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
0b1010
);
} }
// #[test] #[test]
// fn test_shift_left_with_register() { fn test_shift_left_with_register() {
// let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
// *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1010; *cpu.reg(Register::Rg1) = 0b1010;
*cpu.reg(Register::Rg2) = 3;
// let shl_instr = let shl_instr = Instruction::ShiftLeft(RTypeArgs::new(
// Instruction::ShiftLeft(RTypeArgs::new(Some(Register::Rg1), None, None, Some(Register::Rg1),
// Some(3))); Some(Register::Rg2),
None,
None,
));
// shl_instr.execute(&mut cpu).expect( shl_instr.execute(&mut cpu).expect(
// "Emulator was slain by losing the game while attempting to execute "Emulator was slain by losing the game while attempting to execute instruction",
// instruction", ); );
// assert_eq!( assert_eq!(cpu.get(Register::Rg1), 0b101_0000);
// cpu.get(Register::Rg1).expect("Failed to get register Rg1"), }
// 0b101_0000
// );
// }
#[test] #[test]
fn test_load_lower_immediate() { fn test_load_lower_immediate() {
@@ -529,16 +460,13 @@ fn test_load_lower_immediate() {
lli_instr.execute(&mut cpu).expect( lli_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg1), 0x0000_1234);
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
0x0000_1234
);
} }
#[test] #[test]
fn test_load_upper_immediate() { fn test_load_upper_immediate() {
let mut cpu = create_test_processor(); 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( let lui_instr = Instruction::LoadUpperImmediate(ITypeArgs::new(
0x1234, 0x1234,
@@ -549,38 +477,29 @@ fn test_load_upper_immediate() {
lui_instr.execute(&mut cpu).expect( lui_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg1), 0x1234_5678);
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
0x1234_5678
);
} }
#[test] #[test]
fn test_jump_unconditional() { fn test_jump_unconditional() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1000; *cpu.reg(Register::Rg1) = 0x1000;
let initial_pc = cpu.get(Register::Pcx).expect("Failed to get register Pcx"); let initial_pc = cpu.get(Register::Pcx);
let jump_instr = Instruction::Jump(ITypeArgs::new(0x100, Some(Register::Rg1), None)); let jump_instr = Instruction::Jump(ITypeArgs::new(0x100, Some(Register::Rg1), None));
jump_instr.execute(&mut cpu).expect( jump_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Pcx), 0x1100);
cpu.get(Register::Pcx).expect("Failed to get register Pcx"), assert_ne!(cpu.get(Register::Pcx), initial_pc);
0x1100
);
assert_ne!(
cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
initial_pc
);
} }
#[test] #[test]
fn test_jump_equal_when_flag_set() { fn test_jump_equal_when_flag_set() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
cpu.set_flag(Flag::Equal, true); 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 = let jump_eq_instr =
Instruction::JumpEq(ITypeArgs::new(0x100, Some(Register::Rg1), None)); Instruction::JumpEq(ITypeArgs::new(0x100, Some(Register::Rg1), None));
@@ -588,18 +507,15 @@ fn test_jump_equal_when_flag_set() {
jump_eq_instr.execute(&mut cpu).expect( jump_eq_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Pcx), 0x1100);
cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
0x1100
);
} }
#[test] #[test]
fn test_jump_equal_when_flag_not_set() { fn test_jump_equal_when_flag_not_set() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
cpu.set_flag(Flag::Equal, false); cpu.set_flag(Flag::Equal, false);
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1000; *cpu.reg(Register::Rg1) = 0x1000;
let initial_pc = cpu.get(Register::Pcx).expect("Failed to get register Pcx"); let initial_pc = cpu.get(Register::Pcx);
let jump_eq_instr = let jump_eq_instr =
Instruction::JumpEq(ITypeArgs::new(0x100, Some(Register::Rg1), None)); Instruction::JumpEq(ITypeArgs::new(0x100, Some(Register::Rg1), None));
@@ -607,10 +523,7 @@ fn test_jump_equal_when_flag_not_set() {
jump_eq_instr.execute(&mut cpu).expect( jump_eq_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Pcx), initial_pc);
cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
initial_pc
);
} }
#[test] #[test]
@@ -627,8 +540,8 @@ fn test_halt_instruction() {
#[test] #[test]
fn test_nand_instruction() { fn test_nand_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100; *cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010; *cpu.reg(Register::Rg2) = 0b1010;
let nand_instr = Instruction::Nand(RTypeArgs::new( let nand_instr = Instruction::Nand(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -640,17 +553,14 @@ fn test_nand_instruction() {
nand_instr.execute(&mut cpu).expect( nand_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg3), !0b1000);
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
!0b1000
);
} }
#[test] #[test]
fn test_nor_instruction() { fn test_nor_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100; *cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010; *cpu.reg(Register::Rg2) = 0b1010;
let nor_instr = Instruction::Nor(RTypeArgs::new( let nor_instr = Instruction::Nor(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -662,17 +572,14 @@ fn test_nor_instruction() {
nor_instr.execute(&mut cpu).expect( nor_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg3), !0b1110);
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
!0b1110
);
} }
#[test] #[test]
fn test_xnor_instruction() { fn test_xnor_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100; *cpu.reg(Register::Rg1) = 0b1100;
*cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010; *cpu.reg(Register::Rg2) = 0b1010;
let xnor_instr = Instruction::Xnor(RTypeArgs::new( let xnor_instr = Instruction::Xnor(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -684,8 +591,5 @@ fn test_xnor_instruction() {
xnor_instr.execute(&mut cpu).expect( xnor_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!( assert_eq!(cpu.get(Register::Rg3), !0b0110);
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
!0b0110
);
} }
+6 -14
View File
@@ -133,25 +133,17 @@ impl Component for ControlPanel {
} }
)); ));
let pcx = state let pcx = state.reg_file.get(Register::Pcx);
.reg_file
.get(Register::Pcx)
.expect("PCX should never be invalid");
let instructions = state.instructions; let instructions = state.instructions;
ui.label(format!("Instructions: {instructions}")); ui.label(format!("Instructions: {instructions}"));
ui.label(format!("PC: 0x{pcx:08X}")); ui.label(format!("PC: 0x{pcx:08X}"));
let instruction = Instruction::decode( let instruction = Instruction::decode(state.reg_file.get(Register::Cir))
state .map_or_else(
.reg_file |_| "Invalid Instruction".to_string(),
.get(Register::Cir) |instruction| instruction.to_string(),
.expect("CIR should never be invalid"), );
)
.map_or_else(
|_| "Invalid Instruction".to_string(),
|instruction| instruction.to_string(),
);
ui.label(format!("Instruction: {instruction}")); ui.label(format!("Instruction: {instruction}"));
}); });
+20 -32
View File
@@ -16,7 +16,7 @@ use crate::emulator::{
ui::interface::Component, ui::interface::Component,
}; };
use assembler::prelude::*; // use assembler::prelude::*;
#[derive(Default)] #[derive(Default)]
pub struct Editor { pub struct Editor {
@@ -117,7 +117,10 @@ impl Editor {
.file_name() .file_name()
.unwrap_or_else(|| OsStr::new("Unnamed!")) .unwrap_or_else(|| OsStr::new("Unnamed!"))
.to_str() .to_str()
.unwrap_or_else(|| unreachable!("File name should be valid UTF-8.")); .map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
} }
"Unnamed!" "Unnamed!"
} }
@@ -126,9 +129,12 @@ impl Editor {
if let Some(path) = &self.path { if let Some(path) = &self.path {
return path return path
.extension() .extension()
.unwrap_or_else(|| OsStr::new("Unknown!")) .map_or_else(|| OsStr::new("Unknown!"), |ext| ext)
.to_str() .to_str()
.unwrap_or_else(|| unreachable!("File name should be valid UTF-8.")); .map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
} }
"Unknown!" "Unknown!"
} }
@@ -387,20 +393,25 @@ impl Editor {
fn render_editor(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) { fn render_editor(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
let available_width = ui.available_width(); let available_width = ui.available_width();
let syntax = match self.extension() { let syntax = match self.extension() {
"dsa" => Syntax::dsa(), "dsa" => Some(Syntax::new("dsa")),
"dsc" => Syntax::dsc(), _ => None,
_ => Syntax::default(),
}; };
let mut editor = CodeEditor::default() let ed = CodeEditor::default()
.id_source("editor") .id_source("editor")
.with_fontsize(12.0) .with_fontsize(12.0)
.with_rows(0) .with_rows(0)
.with_theme(ColorTheme::default()) .with_theme(ColorTheme::default())
.with_syntax(syntax) .with_syntax(Syntax::dsa())
.with_numlines(true) .with_numlines(true)
.desired_width(available_width - 500.0); .desired_width(available_width - 500.0);
let mut editor = ed.clone();
if let Some(syntax) = syntax {
editor = ed.with_syntax(syntax);
}
editor.show(ui, &mut self.text); editor.show(ui, &mut self.text);
} }
@@ -440,29 +451,6 @@ impl Editor {
.flat_map(|i| i.encode().to_be_bytes().to_vec()) .flat_map(|i| i.encode().to_be_bytes().to_vec())
.collect(); .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") => { Some("dsb") => {
if let Ok(bytes) = fs::read(path) { if let Ok(bytes) = fs::read(path) {
self.output = bytes; self.output = bytes;
+1 -5
View File
@@ -1,4 +1,3 @@
use common::prelude::Instruction;
use egui::{Context, Ui}; use egui::{Context, Ui};
use crate::emulator::{ use crate::emulator::{
@@ -58,11 +57,8 @@ impl Component for History {
.color(egui::Color32::from_rgb(255, 200, 200)), .color(egui::Color32::from_rgb(255, 200, 200)),
); );
let decoded = Instruction::decode(instruction.1)
.unwrap_or(Instruction::Nop);
ui.label( ui.label(
egui::RichText::new(decoded.to_string()) egui::RichText::new(instruction.1.to_string())
.font(egui::FontId::monospace(12.0)) .font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(200, 255, 200)), .color(egui::Color32::from_rgb(200, 255, 200)),
); );
+4 -1
View File
@@ -79,7 +79,10 @@ impl Loader {
.file_name() .file_name()
.unwrap_or_else(|| OsStr::new("Unnamed!")) .unwrap_or_else(|| OsStr::new("Unnamed!"))
.to_str() .to_str()
.unwrap_or_else(|| unreachable!("File name should be valid UTF-8.")); .map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
} }
"Unnamed!" "Unnamed!"
} }
+4 -3
View File
@@ -51,6 +51,7 @@ impl Component for StackInspector {
ui.label("Address"); ui.label("Address");
ui.label("Value"); ui.label("Value");
ui.end_row(); ui.end_row();
for (i, value) in for (i, value) in
state.stack_view.chunks(4).take(32).enumerate() 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.", "Could not read 4 byte instruction or data! Something is wrong.",
)); ));
ui.label(format!( ui.label(format!(
"+{} [{}]", "{} [{}]",
i*4, i,
state.reg_file.get(Register::Spr).expect("SPR should never be invalid") + i as u32 * 4 state.reg_file.get(Register::Spr) - i as u32 * 4
)); ));
ui.label(format!("0x{value:08X} ({value})")); ui.label(format!("0x{value:08X} ({value})"));
ui.end_row(); 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"));
}
// ...
}
```
+1 -1
View File
@@ -2,7 +2,7 @@
// a simple brainf##k interpreter, // a simple brainf##k interpreter,
// because I already wrote a compiler lol. // because I already wrote a compiler lol.
include print "./lib/io/print.dsa" include print "./lib/print.dsa"
// "print hello world" // "print hello world"
db program: "++++++++++++++++++++++++++++++++++++++++++++ db program: "++++++++++++++++++++++++++++++++++++++++++++
Binary file not shown.
-30
View File
@@ -1,30 +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;
}

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