80 Commits

Author SHA1 Message Date
zxq5 7b18922cc7 Merge pull request 'Merge compiler and emulator progress from last few months into main.' (#11) from compiler into main
Reviewed-on: #11
2026-02-14 11:54:15 +00:00
zxq5 a0b02cb955 Create tasks.json 2026-02-14 11:50:59 +00:00
zxq5 240f0e553f fixed failing tests
TODO: add comprehensive testing to everything
2026-02-14 11:50:54 +00:00
zxq5 25a59a6b19 fixed clippy lints 2026-02-14 11:50:36 +00:00
zxq5 c67217a6b8 Merge branch 'main' into compiler 2026-02-14 11:09:33 +00:00
zxq5 7ccbd9258f - fixed some clippy lints
- updated comments in compiler codegen
- deleted old dsa compiler outputs
- settings for zed
2026-02-14 11:05:15 +00:00
zxq5 201b18069b continued on register allocator rewrite, slow progress as scoping is
proving to be a challenge
2026-02-14 02:46:29 +00:00
zxq5 d66baf6f99 moved loc 2026-02-13 21:42:59 +00:00
zxq5 75ad04cf95 forgot to commit these 2026-02-10 16:37:33 +00:00
zxq5 8361833b1c broken commit, started working on scopes 2026-02-10 16:33:32 +00:00
zxq5 5e575e2cd8 used claude to write a language spec (syntax and simple examples) for
DSC that we can follow as a reference for implementation.
2026-02-10 10:05:13 +00:00
zxq5 931af90789 - renamed assembler_runner to just assembler
- implemented type parsing including custom types and generics (useless
  for now as we do no semantic analysis)
- implemented struct literal parsing
- implemented struct definition parsing (no generics yet)
- implemented tuple parsing
- registers are now allocated starting from zero
- updated to-dos
2026-02-10 10:03:48 +00:00
zxq5 509b3465f1 docs update 2026-02-09 00:10:49 +00:00
zxq5 22241a5633 - implementation of <var> <op> = <expr> type statements such as `x +=
5`
- implementation of logical and shift operations in parser and codegen.
- implementation of sizeof keyword as unary operator

in progress (non functional)
- implementation of prefix and postfix inc/dec operators

- array access by index (implemented, untested as arrays aren't
  implemented yet). essentially just a pointer write with offset.
- struct/member access (parsing implemented, untested.)
2026-02-09 00:10:37 +00:00
zxq5 e2be83414b - updated assembler to support new shift implementation
- updated emulator to support new shift implementation
- updated emulator to rename NoReg to Null as in the common lib
2026-02-09 00:05:45 +00:00
zxq5 f7ed764e96 renamed NoReg to Null in common 2026-02-09 00:04:19 +00:00
zxq5 328741eb51 updated compiler with support for more operators.
(only the unary operators from this are implemented for now)
2026-02-08 20:03:31 +00:00
zxq5 9f35fc9415 block allocator implementation and example 2026-02-08 12:36:49 +00:00
zxq5 828f5bfb2d fixed pointers and stuff. 2026-02-08 11:45:26 +00:00
zxq5 6699333b2c - C frontend broken for now
- If statements work properly now (hopefully)
- still issues with while loops pushing vars to the stack. need scoping
  implemented to fix this!

- refactored registers.rs and fixed faulty logic.
- made register allocation optimisations
2026-02-08 00:14:18 +00:00
zxq5 e9329eca95 update roadmap and ISA spec 2026-02-07 18:21:37 +00:00
zxq5 250b780e14 fix broken build system commit 2026-02-07 18:20:59 +00:00
zxq5 bbcef7178f updated assembler to write to binary files correctly 🤦 2026-02-06 15:15:10 +00:00
zxq5 1fcfb3120b started working on build system 2026-02-05 03:12:44 +00:00
zxq5 e69514e46e modified editor to include syntax for .dsc files 2026-02-05 01:26:37 +00:00
zxq5 b8abbfd02f added a brainf&&k module to the compiler (specialised module so no
frontend/backend distinction or use of standard model)
2026-02-05 01:11:38 +00:00
zxq5 c2bf9f6667 added a (very incomplete) C frontend for DSAC 2026-02-05 01:10:47 +00:00
zxq5 2f91c4127c reorganised code examples 2026-02-05 01:10:31 +00:00
zxq5 89762b54e3 updated docs 2026-02-05 01:09:38 +00:00
zxq5 a35cfbe864 updated compiler to support multiple frontends and backends 2026-02-05 01:09:14 +00:00
zxq5 8d130a870c deleted the c compiler 2026-02-05 01:07:59 +00:00
zxq5 a1099249e9 updated roadmap 2026-02-04 01:59:50 +00:00
zxq5 cb65a928c8 fixed bug where stack inspector shows incorrect addresses 2026-02-04 01:59:43 +00:00
zxq5 fa8aa1cd29 integrated compiler in DSA editor 2026-02-04 01:58:55 +00:00
zxq5 7780f5804f deleted old files and modified some dsa source files 2026-02-04 01:58:37 +00:00
zxq5 889ee8ef71 wrote dsa/dsc code examples including an allocator 2026-02-04 01:58:03 +00:00
zxq5 dd20401ad6 added basic logging to common
TODO: improve logging
2026-02-04 01:57:40 +00:00
zxq5 f4933b55fb forgot to commit this 2026-02-04 01:57:18 +00:00
zxq5 14a04a524c added support for DSA libraries to compiler and made some optimisations.
provided an API for the editor to use.
2026-02-04 01:56:58 +00:00
zxq5 f25db6c8fd updated assembler logging 2026-02-04 01:56:15 +00:00
zxq5 48a74bfde2 updated dsc example to reflect current feature set. 2026-02-03 15:38:40 +00:00
zxq5 7973b2afca - refactored lexer
- updated lexer to allow hex and binary integer literals
- updated parser with support for writing to pointers
- updated code generation to support writing to pointers
- fixed a bug with codegen where args are loaded from incorrect offsets
  due to saving registers prior to calling.
2026-02-03 15:37:38 +00:00
zxq5 ce2eda72a0 updated roadmap with progress 2026-02-03 15:34:35 +00:00
zxq5 3afeafc9d4 - compiler works for basic maths expressions and functions
- basic pointers and reading values from pointers works
- writing to pointers not yet implemented (looks painful so a problem
  for tomorrow)
- updated print library. the compiler has this hardcoded in all programs
  for now
2026-02-03 02:11:30 +00:00
zxq5 5573c5a609 minor code changes for codegen 2026-02-02 11:15:45 +00:00
zxq5 8f7163c459 added some documentation and started on compiler for custom language (not C) based on previous prototypes. pretty broken state rn. 2026-02-01 22:16:09 +00:00
zxq5 52ef7872f0 compiler working for some mathematical expressions, function calls and
simple conditionals
2026-01-31 13:28:42 +00:00
zxq5 e31deb594f fixed a bug with the multiply function in core.dsa and added a print_num
function to print.dsa for decimal numbers
2026-01-31 13:28:11 +00:00
zxq5 63c9d858b4 added a to-do list and bacon.toml for developing the compiler 2026-01-31 13:27:31 +00:00
zxq5 782c842a42 updated gitignore 2026-01-29 19:33:30 +00:00
zxq5 259746558f codegen progress 2026-01-29 19:29:48 +00:00
zxq5 b9f98bff7b started on codegen, scaffolding basically done 2025-11-15 02:58:36 +00:00
zxq5 091dabfbf3 Merge remote-tracking branch 'refs/remotes/origin/compiler' into compiler 2025-11-14 23:37:57 +00:00
zxq5 fd5b305576 started work on c compiler 2025-11-14 23:36:51 +00:00
zxq5 b33fdbfeec rust macros make me want to unalive. 2025-07-01 01:08:37 +01:00
zxq5 2582ad10fa started work on compiler 2025-06-30 20:44:39 +01:00
nullndvoid 458661b02a misc: add 'profiling' profile. 2025-06-29 04:11:41 +01:00
nullndvoid c41e5328e6 docs: fix failing doctest 2025-06-29 02:21:31 +01:00
zxq5 67ebf48d6f removed old log file 2025-06-29 02:08:06 +01:00
zxq5 98668c681e more optimisations test program ~54MIPS -> ~110MIPS 2025-06-29 02:04:14 +01:00
zxq5 05a25447b2 minor optimisation to reduce unnecessary allocations 2025-06-28 03:43:20 +01:00
zxq5 56d2abe17f - optimised main emulator loop, allowing updates only once every roughly 32,000 instructions.
- optimised memory access patterns, removing unecessary mutability and accesses.
- replaced the standard HashMap with an implementation that uses a faster hashing algorithm.

results:

before:
    - our benchmark program with ~4m instructions would take around for their data to make it to the UI, and a bit over 200ms to actually run

after:
    - our benchmark program with ~4m instructions can run in around 75ms, and the UI receives the update almost instantly.

conclusion:
- emulator performance should be around 2-3x faster than before.
2025-06-28 03:21:46 +01:00
zxq5 eaaefd1b07 added rule to .gitignore 2025-06-27 18:31:52 +01:00
zxq5 5302ad3876 removed junk files 2025-06-27 18:30:53 +01:00
zxq5 2280f1e5d9 updated vscode settings 2025-06-27 18:30:26 +01:00
zxq5 ae92510fb8 dsa lib bugfixes 2025-06-26 20:53:51 +01:00
zxq5 7c63340888 minor changes to assembler 2025-06-26 20:53:22 +01:00
zxq5 e9f04824ea updates to dsa libs 2025-06-26 00:51:20 +01:00
zxq5 620584488b fixed unit tests & misc changes to workspace config 2025-06-26 00:50:58 +01:00
zxq5 1101331f70 fixed a couple of emulator bugs, including fixing shift instructions. finished implementing lib/io/print/print_hex_byte and print_hex_word 2025-06-25 16:31:42 +01:00
zxq5 c171b0db89 worked on print.dsa and maths/core.dsa 2025-06-25 00:40:31 +01:00
zxq5 82b99c127c finished initial interrupts implementation 2025-06-25 00:13:55 +01:00
nullndvoid 92c4660a4d misc: get rid of some errors from Cargo lol 2025-06-24 21:55:11 +01:00
zxq5 2a6991fe4a idk, i refactored some stuff ig 2025-06-24 19:34:45 +01:00
zxq5 0fdd28aad1 finished the interpreter 2025-06-24 19:31:55 +01:00
zxq5 f639240b6c progress on debugging bf.dsa 2025-06-24 18:07:33 +01:00
zxq5 d2c1492dca added step(n) feature to emulator, allowing for stepping n instructions at a time 2025-06-24 18:07:11 +01:00
zxq5 4ef8bbdf46 updated dependencies 2025-06-24 18:06:29 +01:00
zxq5 76197fac8f finished refactor of emulator - started on loader (needs significant changes before functional in the way that I would like) 2025-06-23 23:45:47 +01:00
zxq5 bc5ddef311 added error handling to emulator 2025-06-23 21:28:38 +01:00
108 changed files with 14951 additions and 9616 deletions
+11
View File
@@ -0,0 +1,11 @@
[build]
rustc-wrapper = "sccache"
# Enable to cut unused deps.
# rustflags = ["-D", "unused-crate-dependencies"]
[future-incompat-report]
frequency = "always"
[profile.profiling]
inherits = "release"
debug = true
+2 -1
View File
@@ -1,2 +1,3 @@
/target /target
**/*.env **/*.env
Cargo.lock
+7 -1
View File
@@ -5,5 +5,11 @@
"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
@@ -0,0 +1,15 @@
// 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
@@ -0,0 +1,37 @@
[
{
"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
-4261
View File
File diff suppressed because it is too large Load Diff
+14 -1
View File
@@ -1,8 +1,21 @@
cargo-features = ["codegen-backend"]
[workspace] [workspace]
members = ["emulator", "common", "assembler", "dsa_editor"] members = ["emulator", "common", "assembler", "dsa_editor", "compiler", "dsx-build"]
resolver = "3" resolver = "3"
[workspace.package] [workspace.package]
version = "0.2.0" version = "0.2.0"
edition = "2024" edition = "2024"
authors = ["zxq5", "nullndvoid"] authors = ["zxq5", "nullndvoid"]
[profile.dev]
codegen-backend = "cranelift"
panic = "abort" # Cranelift does not support stack unwinds.
lto = false
debug = true
incremental = false # sccache does not support caching incremental crates.
[profile.release]
debug = true
lto = "fat"
+1 -1
View File
@@ -5,7 +5,7 @@ edition.workspace = true
authors.workspace = true authors.workspace = true
[[bin]] [[bin]]
name = "assembler_runner" name = "assembler"
path = "src/main.rs" path = "src/main.rs"
[lib] [lib]
-34
View File
@@ -1,34 +0,0 @@
++++++++++++++++++++++++++++++++++++++++++++ c1v44 : ASCII code of comma
>++++++++++++++++++++++++++++++++ c2v32 : ASCII code of space
>++++++++++++++++ c3v11 : quantity of numbers to be calculated
> c4v0 : zeroth Fibonacci number (will not be printed)
>+ c5v1 : first Fibonacci number
<< c3 : loop counter
[ block : loop to print (i)th number and calculate next one
>> c5 : the number to be printed
block : divide c5 by 10 (preserve c5)
> c6v0 : service zero
>++++++++++ c7v10 : divisor
<< c5 : back to dividend
[->+>-[>+>>]>[+[-<+>]>+>>]<<<<<<] c5v0 : divmod algo; results in 0 n d_n%d n%d n/d
>[<+>-] c5 : move dividend back to c5 and clear c6
>[-] c7v0 : clear c7
>> block : c9 can have two digits; divide it by ten again
>++++++++++ c10v10: divisor
< c9 : back to dividend
[->-[>+>>]>[+[-<+>]>+>>]<<<<<] c9v0 : another divmod algo; results in 0 d_n%d n%d n/d
>[-] c10v0 : clear c10
>>[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]c12v0 : print nonzero n/d (first digit) and clear c12
<[++++++++++++++++++++++++++++++++++++++++++++++++.[-]] c11v0 : print nonzero n%d (second digit) and clear c11
<<<++++++++++++++++++++++++++++++++++++++++++++++++.[-] c8v0 : print any n%d (last digit) and clear c8
<<<<<<<.>. c1c2 : print comma and space
block : actually calculate next Fibonacci in c6
>>[>>+<<-] c4v0 : move c4 to c6 (don't need to preserve it)
>[>+<<+>-] c5v0 : move c5 to c6 and c4 (need to preserve it)
>[<+>-] c6v0 : move c6 with sum to c5
<<<- c3 : decrement loop counter
]
<<++... c1 : output three dots
Binary file not shown.
+20 -12
View File
@@ -11,14 +11,10 @@ pub fn codegen(nodes: Vec<Node>) -> Result<Vec<Instruction>, AssembleError> {
let mut instructions = vec![]; let mut instructions = vec![];
for node in nodes { for node in nodes {
instructions.push( instructions.push(build_instruction(&node)?);
build_instruction(&node)
.unwrap_or_else(|_| panic!("Failed to build instruction: {node:?}")),
);
} }
println!("------------------------"); log("Assembly Successful ✅");
log("Compilation Success ✅");
Ok(instructions) Ok(instructions)
} }
@@ -227,19 +223,31 @@ fn build_shift_instruction(
opcode: Opcode, opcode: Opcode,
args: &[crate::assembler::model::Token], args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> { ) -> Result<Instruction, AssembleError> {
let Some(reg_token) = args.first() else { let Some(src_reg) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
}; };
let Some(amount_token) = args.get(1) else { 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)); return Err(AssembleError::MissingArgument(1));
}; };
let reg = expect_token!(reg_token, Register)?; let src = expect_token!(src_reg, Register)?;
let amount = expect_token!(amount_token, Immediate)? as u8; 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 { match opcode {
Opcode::Shl => Ok(Instruction::ShiftLeft(args!(R, sr1: reg, shamt: amount))), Opcode::Shl => Ok(Instruction::ShiftLeft(
Opcode::Shr => Ok(Instruction::ShiftRight(args!(R, sr1: reg, shamt: amount))), 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!(), _ => unreachable!(),
} }
} }
+11 -3
View File
@@ -7,12 +7,11 @@ use common::prelude::Register;
pub fn lexer(mut program: String, module: u64) -> Result<Vec<Token>, AssembleError> { pub fn lexer(mut program: String, module: u64) -> Result<Vec<Token>, AssembleError> {
let mut tokens = Vec::new(); let mut tokens = Vec::new();
program = program.replace(',', "");
let lines = program.lines(); let lines = program.lines();
let mut literal = String::new(); let mut literal = String::new();
for line in lines { for line in lines {
for token in line.split_whitespace() { for (i, token) in line.split_whitespace().enumerate() {
if token.starts_with("//") { if token.starts_with("//") {
break; break;
} }
@@ -23,7 +22,9 @@ pub fn lexer(mut program: String, module: u64) -> Result<Vec<Token>, AssembleErr
if !literal.is_empty() { if !literal.is_empty() {
if !token.starts_with('"') { if !token.starts_with('"') {
literal.push(' '); if i > 0 {
literal.push(' ');
}
literal.push_str(token); literal.push_str(token);
} }
@@ -37,6 +38,11 @@ pub fn lexer(mut program: String, module: u64) -> Result<Vec<Token>, AssembleErr
continue; continue;
} }
let token = token.trim_end_matches(',');
if token.is_empty() {
continue;
}
if let Some(token) = parse_register(token)? { if let Some(token) = parse_register(token)? {
tokens.push(token); tokens.push(token);
} else if let Some(token) = parse_opcode(token)? { } else if let Some(token) = parse_opcode(token)? {
@@ -59,6 +65,8 @@ pub fn lexer(mut program: String, module: u64) -> Result<Vec<Token>, AssembleErr
} }
} }
// println!("{:#?}", tokens);
Ok(tokens) Ok(tokens)
} }
pub fn parse_register(token: &str) -> Result<Option<Token>, AssembleError> { pub fn parse_register(token: &str) -> Result<Option<Token>, AssembleError> {
+2 -2
View File
@@ -1,11 +1,11 @@
//! Macros used throughout the assembler //! Macros used throughout the assembler
use crate::assembler::model::{Node, Opcode, Symbol, Token}; use crate::assembler::model::{Node, Opcode, Symbol, Token};
/// Parse DSA assembly code with optional formatting /// Parse DSA assembly code with optional formatting
/// ///
/// # Examples /// # Examples
/// ``` /// ```rs
/// use assembler::macros::dsa;
/// // With formatting: /// // With formatting:
/// let nodes = dsa!(hash, "mov r1, {}", 42)?; /// let nodes = dsa!(hash, "mov r1, {}", 42)?;
/// ///
+9 -12
View File
@@ -9,13 +9,9 @@ use std::{
thread, thread,
}; };
pub use common::logging::log;
use common::prelude::Instruction; use common::prelude::Instruction;
// TODO: Use an actual logging or tracing library for pretty (scoped) output.
fn log(message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}");
}
// Module declarations // Module declarations
#[macro_use] #[macro_use]
pub mod macros; pub mod macros;
@@ -138,7 +134,11 @@ fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
create_sections(&mut nodes)?; create_sections(&mut nodes)?;
resolve_symbols(&mut nodes)?; resolve_symbols(&mut nodes)?;
log("Generating assembly output...");
let instructions = codegen(nodes)?; let instructions = codegen(nodes)?;
log("Compilation Successful");
Ok(instructions) Ok(instructions)
} }
@@ -187,10 +187,7 @@ fn prepare_dependency(
let deps = Parser::get_dependencies(&nodes, path)?; let deps = Parser::get_dependencies(&nodes, path)?;
log(&format!( log(&format!("{:20} {:20}", "Expanding Pseudo-ops", filename));
"{:20} {:20}",
"Expanding PseudoInstructions", filename
));
// add a section instruction // add a section instruction
nodes.insert( nodes.insert(
@@ -198,9 +195,9 @@ fn prepare_dependency(
node!(None, Opcode::Segment, Token::Immediate(file_hash as u32)), node!(None, Opcode::Segment, Token::Immediate(file_hash as u32)),
); );
for n in &nodes { // for n in &nodes {
println!("{n}"); // println!("{n}");
} // }
program.add_module(nodes); program.add_module(nodes);
+22 -3
View File
@@ -51,19 +51,26 @@ impl fmt::Display for Node {
.as_ref() .as_ref()
.map_or_else(String::new, |symbol| format!("{symbol}:\n")); .map_or_else(String::new, |symbol| format!("{symbol}:\n"));
let args = self
.args()
.into_iter()
.map(|arg| arg.to_string())
.collect::<Vec<_>>()
.join(" ");
write!( write!(
f, f,
"\x1b[93m{} \t\x1b[94m{} \x1b[37m{:?} \x1b[0m", "\x1b[93m{} \t\x1b[94m{} \x1b[37m{} \x1b[0m",
symbol, symbol,
self.opcode(), self.opcode(),
self.args() args,
) )
} }
} }
impl fmt::Display for Symbol { impl fmt::Display for Symbol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} ( module: {})", self.name, self.module) write!(f, "{} [ID:{}]", self.name, self.module)
} }
} }
@@ -174,6 +181,18 @@ pub enum Token {
Opcode(Opcode), 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)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum TokenType { pub enum TokenType {
Symbol, Symbol,
+54 -17
View File
@@ -1,5 +1,6 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use crate::assembler::TokenType;
use crate::{assembler::AssembleError, expect_token, expect_type, node}; use crate::{assembler::AssembleError, expect_token, expect_type, node};
use crate::assembler::model::{Node, Opcode, Token}; use crate::assembler::model::{Node, Opcode, Token};
@@ -100,6 +101,7 @@ impl Parser {
let opcode = expect_token!(self.next()?, Opcode)?; let opcode = expect_token!(self.next()?, Opcode)?;
let args: Vec<Token>; let args: Vec<Token>;
#[allow(clippy::match_same_arms)]
match opcode { match opcode {
// R-type instructions // R-type instructions
Opcode::Mov | Opcode::Movs => { Opcode::Mov | Opcode::Movs => {
@@ -112,24 +114,25 @@ impl Parser {
let base = expect_type!(self.next()?, Register, Symbol)?; let base = expect_type!(self.next()?, Register, Symbol)?;
let dest = expect_type!(self.next()?, Register)?; let dest = expect_type!(self.next()?, Register)?;
let mut offset = Token::Immediate(0); let offset = match self.peek_next() {
if let Ok(next) = self.peek_next() { Ok(next) if expect_type!(next.clone(), Immediate).is_ok() => {
if expect_type!(next, Immediate).is_ok() { self.next()?
offset = self.next()?;
} }
} _ => Token::Immediate(0),
};
args = vec![base, dest, offset]; args = vec![base, dest, offset];
} }
Opcode::Stb | Opcode::Sth | Opcode::Stw => { Opcode::Stb | Opcode::Sth | Opcode::Stw => {
let base = expect_type!(self.next()?, Register)?; let base = expect_type!(self.next()?, Register)?;
let dest = expect_type!(self.next()?, Register, Symbol)?; let dest = expect_type!(self.next()?, Register, Symbol)?;
let mut offset = Token::Immediate(0);
if let Ok(next) = self.peek_next() { let offset = match self.peek_next() {
if expect_type!(next, Immediate).is_ok() { Ok(next) if expect_type!(next.clone(), Immediate).is_ok() => {
offset = self.next()?; self.next()?
} }
} _ => Token::Immediate(0),
};
args = vec![base, dest, offset]; args = vec![base, dest, offset];
} }
@@ -148,15 +151,49 @@ impl Parser {
} }
Opcode::Not | Opcode::Cmp => { Opcode::Not | Opcode::Cmp => {
let reg1 = expect_type!(self.next()?, Register, Symbol)?; let src = expect_type!(self.next()?, Register, Symbol)?;
let reg2 = expect_type!(self.next()?, Register, Symbol)?; let dest = expect_type!(self.next()?, Register, Symbol)?;
args = vec![reg1, reg2]; args = vec![src, dest];
} }
Opcode::Shl | Opcode::Shr => { Opcode::Shl | Opcode::Shr => {
let reg = expect_type!(self.next()?, Register, Symbol)?; let src = expect_type!(self.next()?, Register, Symbol)?;
let num = expect_type!(self.next()?, Immediate)?;
args = vec![reg, num]; // 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 => { Opcode::Inc | Opcode::Dec => {
+28 -1
View File
@@ -13,11 +13,38 @@
)] )]
pub mod assembler; pub mod assembler;
pub mod brainf; pub mod image_builder;
pub mod tooling; pub mod tooling;
mod util; mod util;
pub mod prelude { pub mod prelude {
pub use crate::assembler::CompilerEngine; pub use crate::assembler::CompilerEngine;
pub use crate::image_builder;
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 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(())
}
+12 -18
View File
@@ -1,4 +1,12 @@
use assembler::{brainf, prelude::*}; use common as _;
use num_cpus as _;
use threadpool as _;
use assembler::{
assemble_file,
prelude::*,
tooling::{brainf, project},
};
use std::{fs, io::Write, path::PathBuf}; use std::{fs, io::Write, path::PathBuf};
fn main() { fn main() {
@@ -16,7 +24,7 @@ fn main() {
let mut file = match fs::File::create("brainf.dsb") { let mut file = match fs::File::create("brainf.dsb") {
Err(e) => { Err(e) => {
eprintln!("Failed to create output file: {}", e); eprintln!("Failed to create output file: {e}");
std::process::exit(1); std::process::exit(1);
} }
Ok(file) => file, Ok(file) => file,
@@ -24,7 +32,7 @@ fn main() {
for instruction in result { for instruction in result {
if let Err(e) = file.write(&instruction.encode().to_be_bytes()) { if let Err(e) = file.write(&instruction.encode().to_be_bytes()) {
eprintln!("Failed to write to output file: {}", e); eprintln!("Failed to write to output file: {e}");
std::process::exit(1); std::process::exit(1);
} }
} }
@@ -39,19 +47,5 @@ fn main() {
let input_path = &args[2]; let input_path = &args[2];
let output_path = &args[4]; let output_path = &args[4];
let src = PathBuf::from(input_path); assemble_file(input_path, output_path).unwrap();
// 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);
}
}
} }
+1
View File
@@ -1 +1,2 @@
pub mod brainf;
pub mod project; pub mod project;
+1
View File
@@ -0,0 +1 @@
disallowed-types = ["std::collections::HashMap", "std::collections::HashSet"]
+21 -17
View File
@@ -3,13 +3,19 @@ use crate::{instructions::encode::Encode, prelude::*};
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Interrupt { pub enum Interrupt {
Software(u8), Software(u8),
Breakpoint,
HardFault,
} }
pub type Address = u32; pub type Address = u32;
impl Interrupt { impl Interrupt {
const fn as_u8(self) -> u8 { // someone tell clippy to stfu.
#[allow(clippy::must_use_candidate)]
pub const fn as_u8(self) -> u8 {
match self { match self {
Self::Breakpoint => 0,
Self::HardFault => 1,
Self::Software(code) => code, Self::Software(code) => code,
} }
} }
@@ -19,10 +25,11 @@ impl Interrupt {
impl From<u8> for Interrupt { impl From<u8> for Interrupt {
#[allow(unreachable_code)] #[allow(unreachable_code)]
fn from(code: u8) -> Self { fn from(code: u8) -> Self {
return Self::Software(code); match code {
todo!("Implement this once a hardware interrupt convention is established."); 0 => Self::Breakpoint,
1 => Self::HardFault,
// Self::Software(_code) _ => Self::Software(code),
}
} }
} }
@@ -33,7 +40,7 @@ pub enum InstructionType {
Immediate, Immediate,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
#[non_exhaustive] #[non_exhaustive]
pub enum Register { pub enum Register {
// general purpose registers // general purpose registers
@@ -62,7 +69,9 @@ 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,
@@ -73,7 +82,8 @@ pub enum Register {
} }
impl Register { impl Register {
#[must_use] // this is here so clippy shuts up about the must_use tag.
#[allow(clippy::must_use_candidate)]
pub fn general() -> Vec<Self> { pub fn general() -> Vec<Self> {
vec![ vec![
Self::Rg0, Self::Rg0,
@@ -96,12 +106,6 @@ 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;
@@ -136,7 +140,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::NoReg, 0x17 => Self::Null,
0x18 => Self::Mar, 0x18 => Self::Mar,
0x19 => Self::Mdr, 0x19 => Self::Mdr,
0x1A => Self::Sts, 0x1A => Self::Sts,
@@ -175,7 +179,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::NoReg), "null" => Ok(Self::Null),
"pcx" => Ok(Self::Pcx), "pcx" => Ok(Self::Pcx),
_ => Err(RegisterParseError::InvalidName(value.to_string())), _ => Err(RegisterParseError::InvalidName(value.to_string())),
} }
@@ -208,7 +212,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::NoReg => write!(f, "noreg"), Self::Null => write!(f, "null"),
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"),
+8 -6
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::NoReg as u32; let sr1 = Register::Null as u32;
let sr2 = Register::NoReg as u32; let sr2 = Register::Null as u32;
let dr = Register::NoReg as u32; let dr = Register::Null 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,12 +54,14 @@ 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());
let segment = segment as u8; (opcode << 26) | u32::from(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::NoReg as u32; let no_reg = Register::Null 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::NoReg as u32; let no_reg = Register::Null 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::NoReg as u32; let no_reg = Register::Null 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::NoReg as u32; let no_reg = Register::Null as u32;
let instruction = Instruction::ShiftLeft(RTypeArgs::new( let instruction = Instruction::ShiftLeft(RTypeArgs::new(
Some(Register::Rg0), Some(Register::Rg0),
+1
View File
@@ -13,6 +13,7 @@
)] )]
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
@@ -0,0 +1,4 @@
// 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
@@ -0,0 +1,10 @@
[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
@@ -0,0 +1,129 @@
# This is a configuration file for the bacon tool
#
# Complete help on configuration: https://dystroy.org/bacon/config/
#
# You may check the current default at
# https://github.com/Canop/bacon/blob/main/defaults/default-bacon.toml
default_job = "check"
[jobs.check]
command = ["cargo", "check", "--color", "always"]
need_stdout = false
[jobs.check-all]
command = ["cargo", "check", "--all-targets", "--color", "always"]
need_stdout = false
# Run clippy on the default target
[jobs.clippy]
command = [
"cargo", "clippy",
"--color", "always",
]
need_stdout = false
# Run clippy on all targets
# To disable some lints, you may change the job this way:
# [jobs.clippy-all]
# command = [
# "cargo", "clippy",
# "--all-targets",
# "--color", "always",
# "--",
# "-A", "clippy::bool_to_int_with_if",
# "-A", "clippy::collapsible_if",
# "-A", "clippy::derive_partial_eq_without_eq",
# ]
# need_stdout = false
[jobs.clippy-all]
command = [
"cargo", "clippy",
"--all-targets",
"--color", "always",
]
need_stdout = false
# This job lets you run
# - all tests: bacon test
# - a specific test: bacon test -- config::test_default_files
# - the tests of a package: bacon test -- -- -p config
[jobs.test]
command = [
"cargo", "test", "--color", "always",
"--", "--color", "always", # see https://github.com/Canop/bacon/issues/124
]
need_stdout = true
[jobs.nextest]
command = [
"cargo", "nextest", "run",
"--color", "always",
"--hide-progress-bar", "--failure-output", "final"
]
need_stdout = true
analyzer = "nextest"
[jobs.doc]
command = ["cargo", "doc", "--color", "always", "--no-deps"]
need_stdout = false
# If the doc compiles, then it opens in your browser and bacon switches
# to the previous job
[jobs.doc-open]
command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"]
need_stdout = false
on_success = "back" # so that we don't open the browser at each change
# You can run your application and have the result displayed in bacon,
# if it makes sense for this crate.
# Don't forget the `--color always` part or the errors won't be
# properly parsed.
[jobs.run]
command = [
"cargo", "run",
"--color", "always",
"--",
"../resources/dsa/example.dsc",
"../resources/dsa/example.dsa"
# put launch parameters for your program behind a `--` separator
]
need_stdout = true
allow_warnings = true
background = true
# Run your long-running application (eg server) and have the result displayed in bacon.
# For programs that never stop (eg a server), `background` is set to false
# to have the cargo run output immediately displayed instead of waiting for
# program's end.
# 'on_change_strategy' is set to `kill_then_restart` to have your program restart
# on every change (an alternative would be to use the 'F5' key manually in bacon).
# If you often use this job, it makes sense to override the 'r' key by adding
# a binding `r = job:run-long` at the end of this file .
[jobs.run-long]
command = [
"cargo", "run",
"--color", "always",
# put launch parameters for your program behind a `--` separator
]
need_stdout = true
allow_warnings = true
background = false
on_change_strategy = "kill_then_restart"
# This parameterized job runs the example of your choice, as soon
# as the code compiles.
# Call it as
# bacon ex -- my-example
[jobs.ex]
command = ["cargo", "run", "--color", "always", "--example"]
need_stdout = true
allow_warnings = true
# You may define here keybindings that would be specific to
# a project, for example a shortcut to launch a specific job.
# Shortcuts to internal functions (scrolling, toggling, etc.)
# should go in your personal global prefs.toml file instead.
[keybindings]
# alt-m = "job:my-job"
c = "job:clippy-all" # comment this to have 'c' run clippy on only the default target
+955
View File
@@ -0,0 +1,955 @@
use std::collections::HashMap;
use std::sync::atomic::AtomicU32;
use std::time::SystemTime;
use chrono::{DateTime, Local};
use super::registers::RegisterAllocator;
use crate::backend::dsa::instruction::{InsBlock as IB, Instruction as I, Label};
use crate::backend::dsa::registers::Register;
use crate::model::{
AssignmentOperator, BinaryOperator, Call, CompilerError, ConstExpr, Declaration,
Dependency, Expression, Number, Program, Statement, TypeId, UnaryOperator, Variable,
};
pub struct CodeGenerator {
ast: Program,
imports: HashMap<String, I>,
globals: HashMap<String, I>,
functions: Vec<IB>,
symbols: Vec<String>,
allocator: RegisterAllocator,
}
impl CodeGenerator {
pub fn new(ast: Program) -> Self {
CodeGenerator {
ast,
imports: HashMap::new(),
globals: HashMap::new(),
functions: Vec::new(),
symbols: Vec::new(),
allocator: RegisterAllocator::new(),
}
}
pub fn include(&mut self, name: impl Into<String>, path: impl Into<String>) {
let name = name.into();
self.imports.insert(name.clone(), I::include(name, path));
}
fn is_global(&self, name: &str) -> bool {
// Check if this variable is in the globals list
self.globals.contains_key(name)
}
pub fn generate(&mut self) -> Result<String, CompilerError> {
// always include the print library for debugging!
self.include("print", "./lib/io/print.dsa");
for block in self.ast.clone().declarations {
match block {
Declaration::Variable {
var: Variable { name, .. },
..
} => self.symbols.push(name),
Declaration::Function { name, .. } => self.symbols.push(name),
Declaration::Dependency(Dependency { name, .. }) => {
self.symbols.push(name)
}
Declaration::Struct { .. } => {} /* we can't do any code generation for
* a struct yet. we may need to later
* once these become class-like
* objects with implementations */
}
}
for block in self.ast.clone().declarations {
self.generate_block(block.clone())?;
}
let assembly = self.generate_layout()?;
Ok(assembly
.iter()
.map(|i| i.to_string())
.collect::<Vec<_>>()
.join("\n"))
}
fn generate_layout(&mut self) -> Result<IB, CompilerError> {
let datetime: DateTime<Local> = SystemTime::now().into();
let mut block = IB::new();
block.extend(vec![
I::global_comment(format!(
"GENERATED BY DSC COMPILER
Generated at {}",
datetime.format("%Y-%m-%d %H:%M:%S")
)),
I::Newline,
I::global_comment("Imports"),
]);
block.extend(self.imports.values().cloned().collect::<Vec<_>>());
block.extend(vec![
I::Newline,
I::global_comment("Globals & Reserved Memory"),
]);
block.extend(self.globals.values().cloned().collect::<Vec<_>>());
block.extend(vec![
I::Newline,
I::global_comment("Entry Point"),
I::db_word("stack", 0x10000),
I::db_string("message", "Process Exited with code:"),
// init function for stack setup.
I::label("_init"),
I::ldw_label("stack", Register::Bpr),
I::mov(Register::Bpr, Register::Spr),
I::push(Register::Zero),
I::call("main"),
I::call("print::print_newline"),
I::lwi_label("message", Register::Rg0),
I::push(Register::Rg0),
I::call("print::print"),
I::pop(Register::Zero),
I::call("print::print_hex_word"),
I::pop(Register::Zero),
I::Hlt,
I::Newline,
// default return block boilerplate
I::global_comment("Return"),
I::label("_ret"),
I::mov(Register::Bpr, Register::Spr),
I::pop(Register::Bpr),
I::Return,
]);
for function in self.functions.iter() {
block.extend(function.iter().cloned());
}
Ok(block)
}
fn generate_global(&mut self, name: &str, init: Option<ConstExpr>) {
let init = init.unwrap_or(ConstExpr::Number(0));
match init {
ConstExpr::Number(value) => {
self.globals
.insert(name.to_string(), I::db_word(name, value as u32));
}
ConstExpr::String(str) => {
self.globals
.insert(name.to_string(), I::db_string(name, str));
}
}
}
fn generate_block(&mut self, block: Declaration) -> Result<(), CompilerError> {
match block {
Declaration::Variable { var, init, .. } => {
self.generate_global(&var.name, init)
}
Declaration::Function {
name,
params,
body,
return_type,
} => {
let func = self.generate_function(&name, &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
@@ -0,0 +1,797 @@
use std::fmt;
use crate::backend::dsa::registers::Register;
pub struct InsBlock {
instructions: Vec<Instruction>,
}
impl InsBlock {
pub fn new() -> Self {
Self {
instructions: vec![],
}
}
pub fn insert(&mut self, index: usize, instr: Instruction) {
self.instructions.insert(index, instr);
}
pub fn push(&mut self, instr: Instruction) {
self.instructions.push(instr);
}
pub fn append(&mut self, mut other: Self) {
self.instructions.append(&mut other.instructions);
}
pub fn extend(&mut self, instrs: impl IntoIterator<Item = Instruction>) {
self.instructions.extend(instrs);
}
pub fn is_empty(&self) -> bool {
self.instructions.is_empty()
}
pub fn len(&self) -> usize {
self.instructions.len()
}
pub fn iter(&self) -> impl Iterator<Item = &Instruction> {
self.instructions.iter()
}
}
impl From<Vec<Instruction>> for InsBlock {
fn from(instructions: Vec<Instruction>) -> Self {
Self { instructions }
}
}
impl From<Instruction> for InsBlock {
fn from(instr: Instruction) -> Self {
Self {
instructions: vec![instr],
}
}
}
#[derive(Debug, Clone)]
pub enum Instruction {
// Labels and comments
Label(Label),
Comment {
text: String,
top_level: bool,
},
Newline,
// Data Directives
Db {
label: String,
data: Vec<u8>,
},
Dh {
label: String,
data: Vec<u16>,
},
Dw {
label: String,
data: Vec<u32>,
},
DString {
// alias for db.
label: String,
data: String,
},
Resx {
label: String,
size: u32,
},
// Include
Include {
name: String,
path: String,
},
// Data movement
Mov {
src: Register,
dest: Register,
},
Movs {
src: Register,
dest: Register,
},
// Memory operations
Ldb {
src: MemOperand,
dest: Register,
},
Ldh {
src: MemOperand,
dest: Register,
},
Ldw {
src: MemOperand,
dest: Register,
},
Stb {
src: Register,
dest: MemOperand,
},
Sth {
src: Register,
dest: MemOperand,
},
Stw {
src: Register,
dest: MemOperand,
},
// Immediate loads
Lli {
imm: Imm,
dest: Register,
},
Lui {
imm: Imm,
dest: Register,
},
Lwi {
imm: Imm,
dest: Register,
},
LwiLabel {
label: String,
dest: Register,
},
// Arithmetic
Add {
src1: Register,
src2: Register,
dest: Register,
},
Sub {
src1: Register,
src2: Register,
dest: Register,
},
IAdd {
src: Register,
imm: Imm,
dest: Option<Register>,
},
ISub {
src: Register,
imm: Imm,
dest: Option<Register>,
},
Inc {
reg: Register,
},
Dec {
reg: Register,
},
// Bitwise
And {
src1: Register,
src2: Register,
dest: Register,
},
Or {
src1: Register,
src2: Register,
dest: Register,
},
Xor {
src1: Register,
src2: Register,
dest: Register,
},
Not {
src: Register,
dest: Register,
},
Nand {
src1: Register,
src2: Register,
dest: Register,
},
Nor {
src1: Register,
src2: Register,
dest: Register,
},
Xnor {
src1: Register,
src2: Register,
dest: Register,
},
// Shifts
Shl {
src1: Register,
r_shamt: Register,
i_shamt: u16,
dest: Register,
},
Shr {
src1: Register,
r_shamt: Register,
i_shamt: u16,
dest: Register,
},
// Comparison
Cmp {
reg1: Register,
reg2: Register,
},
// Jumps
Jmp {
target: Label,
},
Jeq {
target: Label,
},
Jne {
target: Label,
},
Jgt {
target: Label,
},
Jge {
target: Label,
},
Jlt {
target: Label,
},
Jle {
target: Label,
},
// Stack
Push {
reg: Register,
},
Pop {
reg: Register,
},
// Function calls
Call {
target: String,
}, // namespace::function
Return,
// System
Hlt,
Nop,
Int {
code: u8,
},
}
pub enum DataDirective {
U8(Vec<u8>),
U16(Vec<u16>),
U32(Vec<u32>),
String(String),
Char(char),
}
impl fmt::Display for Instruction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Label(l) => write!(f, "{}:", l),
Self::Newline => write!(f, ""), /* empty string as newlines are inserted */
// automatically.
Self::Comment { text, top_level } => write!(
f,
"{}",
text.lines()
.map(|line| format!(
"{}// {}",
if *top_level { "" } else { " " },
line.trim(),
))
.collect::<Vec<String>>()
.join("\n")
),
Self::Include { name, path } => write!(f, "include {name}: \"{}\"", path),
Self::Db { label, data } => write!(
f,
"db {}: {}",
label,
data.iter()
.map(|&b| format!("{:#04X}", b))
.collect::<Vec<String>>()
.join(", ")
),
Self::Dh { label, data } => write!(
f,
"dh {}: {}",
label,
data.iter()
.map(|&b| format!("{:#06X}", b))
.collect::<Vec<String>>()
.join(", ")
),
Self::Dw { label, data } => write!(
f,
"dw {}: {}",
label,
data.iter()
.map(|&b| format!("{:#08X}", b))
.collect::<Vec<String>>()
.join(", ")
),
Self::DString { label, data } => write!(f, "db {}: \"{}\"", label, data),
Self::Resx { label, size } => write!(f, "resx {}: {}", label, size),
Self::Mov { src, dest } => write!(f, " mov {}, {}", src, dest),
Self::Movs { src, dest } => write!(f, " movs {}, {}", src, dest),
Self::Ldb { src: addr, dest } => {
let (reg, offset) = reg_and_offset(addr);
write!(f, " ldb {}, {}, {}", reg, dest, offset)
}
Self::Ldh { src: addr, dest } => {
let (reg, offset) = reg_and_offset(addr);
write!(f, " ldh {}, {}, {}", reg, dest, offset)
}
Self::Ldw { src, dest } => {
let (reg, offset) = reg_and_offset(src);
write!(f, " ldw {}, {}, {}", reg, dest, offset)
}
// Self::Ldbs { addr, dest } => {
// write!(f, " ldbs {}, {}", format_mem_operand(addr), dest)
// }
// Self::Ldhs { addr, dest } => {
// write!(f, " ldhs {}, {}", format_mem_operand(addr), dest)
// }
// Self::Ldws { addr, dest } => {
// write!(f, " ldws {}, {}", format_mem_operand(addr), dest)
// }
Self::Stb { src, dest: addr } => {
let (reg, offset) = reg_and_offset(addr);
write!(f, " stb {}, {}, {}", src, reg, offset)
}
Self::Sth { src, dest: addr } => {
let (reg, offset) = reg_and_offset(addr);
write!(f, " sth {}, {}, {}", src, reg, offset)
}
Self::Stw { src, dest: addr } => {
let (reg, offset) = reg_and_offset(addr);
write!(f, " stw {}, {}, {}", src, reg, offset)
}
Self::Lli { imm, dest } => write!(f, " lli {}, {}", imm, dest),
Self::Lui { imm, dest } => write!(f, " lui {}, {}", imm, dest),
Self::Lwi { imm, dest } => write!(f, " lwi {}, {}", imm, dest),
Self::LwiLabel { label, dest } => write!(f, " lwi {}, {}", label, dest),
// arithmetic
Self::Add { src1, src2, dest } => {
write!(f, " add {}, {}, {}", src1, src2, dest)
}
Self::Sub { src1, src2, dest } => {
write!(f, " sub {}, {}, {}", src1, src2, dest)
}
Self::And { src1, src2, dest } => {
write!(f, " and {}, {}, {}", src1, src2, dest)
}
Self::Or { src1, src2, dest } => {
write!(f, " or {}, {}, {}", src1, src2, dest)
}
Self::Nand { src1, src2, dest } => {
write!(f, " nand {}, {}, {}", src1, src2, dest)
}
Self::Xor { src1, src2, dest } => {
write!(f, " xor {}, {}, {}", src1, src2, dest)
}
Self::Nor { src1, src2, dest } => {
write!(f, " nor {}, {}, {}", src1, src2, dest)
}
Self::Not { src, dest } => {
write!(f, " not {} {}", src, dest)
}
Self::Xnor { src1, src2, dest } => {
write!(f, " xnor {}, {}, {}", src1, src2, dest)
}
Self::IAdd { src, imm, dest } => {
if let Some(d) = dest {
write!(f, " addi {}, {}, {}", src, imm, d)
} else {
write!(f, " addi {}, {}", src, imm)
}
}
Self::ISub { src, imm, dest } => {
if let Some(d) = dest {
write!(f, " subi {}, {}, {}", src, imm, d)
} else {
write!(f, " subi {}, {}", src, imm)
}
}
// shift instructions
Self::Shl {
src1,
r_shamt,
i_shamt,
dest,
} => {
write!(f, " shl {}, {}, {}, {}", src1, r_shamt, i_shamt, dest)
}
Self::Shr {
src1,
r_shamt,
i_shamt,
dest,
} => {
write!(f, " shl {}, {}, {}, {}", src1, r_shamt, i_shamt, dest)
}
// increment instructions
Self::Inc { reg } => write!(f, " inc {}", reg),
Self::Dec { reg } => write!(f, " dec {}", reg),
Self::Cmp { reg1, reg2 } => write!(f, " cmp {}, {}", reg1, reg2),
// jump instructions
Self::Jmp { target } => write!(f, " jmp {}", target),
Self::Jeq { target } => write!(f, " jeq {}", target),
Self::Jne { target } => write!(f, " jne {}", target),
Self::Jgt { target } => write!(f, " jgt {}", target),
Self::Jge { target } => write!(f, " jge {}", target),
Self::Jlt { target } => write!(f, " jlt {}", target),
Self::Jle { target } => write!(f, " jle {}", target),
// stack pseudoinstructions
Self::Push { reg } => write!(f, " push {}", reg),
Self::Pop { reg } => write!(f, " pop {}", reg),
// call & return pseudoinstructions
Self::Call { target } => write!(f, " call {}", target),
Self::Return => write!(f, " return"),
// misc instructions
Self::Int { code } => write!(f, " int {}", code),
Self::Hlt => write!(f, " hlt"),
Self::Nop => write!(f, " nop"),
}
}
}
impl Instruction {
// data directives
pub fn db_string(label: impl Into<String>, data: impl Into<String>) -> Self {
Self::DString {
label: label.into(),
data: data.into(),
}
}
pub fn db_word(label: impl Into<String>, data: u32) -> Self {
Self::Dw {
label: label.into(),
data: vec![data],
}
}
pub fn db_bytes(label: impl Into<String>, data: &[u8]) -> Self {
Self::Db {
label: label.into(),
data: data.to_vec(),
}
}
// Movement
pub fn mov<R1, R2>(src: R1, dest: R2) -> Self
where
R1: Into<Register>,
R2: Into<Register>,
{
Self::Mov {
src: src.into(),
dest: dest.into(),
}
}
// Memory loads
pub fn ldw_reg<R>(base: R, dest: Register) -> Self
where
R: Into<Register>,
{
Self::Ldw {
src: MemOperand::RegIndirect(base.into()),
dest,
}
}
pub fn ldw_reg_offset<R>(base: R, dest: Register, offset: i32) -> Self
where
R: Into<Register>,
{
Self::Ldw {
src: MemOperand::RegOffset(base.into(), offset),
dest,
}
}
pub fn ldw_label(label: impl Into<Label>, dest: Register) -> Self {
Self::Ldw {
src: MemOperand::Label(label.into()),
dest,
}
}
// Memory stores
pub fn stw_reg<R>(src: Register, base: R) -> Self
where
R: Into<Register>,
{
Self::Stw {
src,
dest: MemOperand::RegIndirect(base.into()),
}
}
pub fn stw_reg_offset<R>(src: Register, base: R, offset: i32) -> Self
where
R: Into<Register>,
{
Self::Stw {
src,
dest: MemOperand::RegOffset(base.into(), offset),
}
}
pub fn stw_label(src: Register, label: impl Into<Label>) -> Self {
Self::Stw {
src,
dest: MemOperand::Label(label.into()),
}
}
// Arithmetic
pub fn add(src1: Register, src2: Register, dest: Register) -> Self {
Self::Add { src1, src2, dest }
}
pub fn sub(src1: Register, src2: Register, dest: Register) -> Self {
Self::Sub { src1, src2, dest }
}
pub fn and(src1: Register, src2: Register, dest: Register) -> Self {
Self::And { src1, src2, dest }
}
pub fn or(src1: Register, src2: Register, dest: Register) -> Self {
Self::Or { src1, src2, dest }
}
pub fn xor(src1: Register, src2: Register, dest: Register) -> Self {
Self::Xor { src1, src2, dest }
}
pub fn not(src: Register, dest: Register) -> Self {
Self::Not { src, dest }
}
pub fn shl(src1: Register, r_shamt: Register, i_shamt: u16, dest: Register) -> Self {
Self::Shl {
src1,
r_shamt,
i_shamt,
dest,
}
}
pub fn shr(src1: Register, r_shamt: Register, i_shamt: u16, dest: Register) -> Self {
Self::Shr {
src1,
r_shamt,
i_shamt,
dest,
}
}
pub fn iadd(src: Register, value: i64) -> Self {
let imm = Imm(value.unsigned_abs() as u32);
if value < 0 {
Self::ISub {
src,
imm,
dest: None,
}
} else {
Self::IAdd {
src,
imm,
dest: None,
}
}
}
pub fn iadd_dest(src: Register, value: i32, dest: Register) -> Self {
let imm = Imm(value.unsigned_abs());
if value < 0 {
Self::ISub {
src,
imm,
dest: Some(dest),
}
} else {
Self::IAdd {
src,
imm,
dest: Some(dest),
}
}
}
pub fn inc(reg: Register) -> Self {
Self::Inc { reg }
}
pub fn dec(reg: Register) -> Self {
Self::Dec { reg }
}
// Immediate loads
pub fn lwi(value: u32, dest: Register) -> Self {
if value > 0xFFFF {
Self::Lwi {
imm: Imm(value),
dest,
}
} else {
Self::Lli {
imm: Imm(value),
dest,
}
}
}
pub fn lwi_label(label: impl Into<String>, dest: Register) -> Self {
Self::LwiLabel {
label: label.into(),
dest,
}
}
// Control flow
pub fn label(name: impl Into<String>) -> Self {
Self::Label(Label(name.into()))
}
pub fn jmp(target: impl Into<Label>) -> Self {
Self::Jmp {
target: target.into(),
}
}
pub fn jeq(target: impl Into<Label>) -> Self {
Self::Jeq {
target: target.into(),
}
}
pub fn cmp(reg1: Register, reg2: Register) -> Self {
Self::Cmp { reg1, reg2 }
}
// Stack
pub fn push(reg: Register) -> Self {
Self::Push { reg }
}
pub fn pop(reg: Register) -> Self {
Self::Pop { reg }
}
// Functions
pub fn call(target: impl Into<String>) -> Self {
Self::Call {
target: target.into(),
}
}
pub fn int(code: u8) -> Self {
Self::Int { code }
}
pub fn ret() -> Self {
Self::Return
}
// Utilities
pub fn comment(text: impl Into<String>) -> Self {
Self::Comment {
text: text.into(),
top_level: false,
}
}
pub fn global_comment(text: impl Into<String>) -> Self {
Self::Comment {
text: text.into(),
top_level: true,
}
}
pub fn include(name: impl Into<String>, path: impl Into<String>) -> Self {
Self::Include {
name: name.into(),
path: path.into(),
}
}
}
// Convenience trait for Label conversion
impl From<String> for Label {
fn from(s: String) -> Self {
Label(s)
}
}
impl From<&str> for Label {
fn from(s: &str) -> Self {
Label(s.to_string())
}
}
fn reg_and_offset(op: &MemOperand) -> (String, i32) {
match op {
MemOperand::RegIndirect(reg) => (reg.to_string(), 0),
MemOperand::RegOffset(reg, offset) => (reg.to_string(), *offset),
MemOperand::Label(label) => (label.to_string(), 0),
MemOperand::LabelOffset(label, offset) => (label.to_string(), *offset),
}
}
/// Memory operand for loads/stores
#[derive(Debug, Clone)]
pub enum MemOperand {
/// Register indirect: [reg]
RegIndirect(Register),
/// Register with offset: [reg + offset]
RegOffset(Register, i32),
/// Label: [label]
Label(Label),
/// Label with offset: [label + offset]
LabelOffset(Label, i32),
}
/// Immediate value (16-bit or 32-bit)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Imm(pub u32);
impl fmt::Display for Imm {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
/// Label reference
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Label(pub String);
impl fmt::Display for Label {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
+12
View File
@@ -0,0 +1,12 @@
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
@@ -0,0 +1,560 @@
use std::{collections::HashMap, fmt};
use crate::{
backend::dsa::instruction::{InsBlock, Instruction},
model::CompilerError,
};
/// Register allocator for DSA assembly generation
/// Manages general-purpose registers (rg0-rgf) and handles stack spilling
pub struct RegisterAllocator {
/// Available general-purpose registers
/// Maps variable names to their current location (register or stack offset)
variable_locations: HashMap<String, Location>,
/// Maps registers to the variables they currently hold
register_contents: HashMap<Register, String>,
/// Current stack offset for local variables (relative to bpr)
/// Starts at -4 (going downward from base pointer)
stack_offset: i32,
/// Track which registers are currently in use
in_use: Vec<(Register, bool)>,
}
#[derive(Debug, Clone)]
pub struct Location {
register: Option<Register>,
stack: Option<i32>,
}
impl Location {
pub fn stack(offset: i32) -> Self {
Location {
register: None,
stack: Some(offset),
}
}
pub fn register(register: Register) -> Self {
Location {
register: Some(register),
stack: None,
}
}
}
impl RegisterAllocator {
pub fn new() -> Self {
// Initialize with available GP registers (rg0-rgf = 16 registers)
let in_use = vec![
Register::Rg0,
Register::Rg1,
Register::Rg2,
Register::Rg3,
Register::Rg4,
Register::Rg5,
Register::Rg6,
Register::Rg7,
Register::Rg8,
Register::Rg9,
Register::Rga,
Register::Rgb,
Register::Rgc,
Register::Rgd,
Register::Rge,
Register::Rgf,
]
.iter()
.map(|&reg| (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
@@ -0,0 +1,287 @@
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
@@ -0,0 +1,93 @@
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
@@ -0,0 +1,13 @@
use crate::model::{CompilerError, Program};
mod dsa;
pub fn compiler_backend(ext: &str, ast: &Program) -> Result<String, CompilerError> {
match ext {
"dsa" => Ok(dsa::generate_code(ast)?),
_ => Err(CompilerError::Generic(format!(
"File type {} not supported",
ext
))),
}
}
+336
View File
@@ -0,0 +1,336 @@
// ============================================================================
// Token Types
// ============================================================================
#[derive(Debug, Clone, PartialEq)]
pub enum TokenType {
// Keywords
Int,
If,
Else,
While,
Return,
Include,
// Identifiers and literals
Identifier(String),
Number(i32),
String(String),
Char(char),
// Operators
Plus,
Minus,
Star,
Slash,
Assign,
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
// Delimiters
LParen,
RParen,
LBrace,
RBrace,
Semicolon,
Comma,
Colon,
Namespace,
Eof,
}
#[allow(unused)]
pub enum Type {
Int32,
Int16,
Int8,
Uint32,
Uint16,
Uint8,
Char,
}
#[derive(Debug, Clone)]
pub struct Token {
pub token_type: TokenType,
pub line: usize,
pub col: usize,
}
impl Token {
pub fn new(token_type: TokenType, line: usize, col: usize) -> Self {
Self {
token_type,
line,
col,
}
}
}
// ============================================================================
// Lexer
// ============================================================================
pub struct Lexer {
source: Vec<char>,
pos: usize,
line: usize,
col: usize,
}
impl Lexer {
pub fn new(source: &str) -> Self {
Self {
source: source.chars().collect(),
pos: 0,
line: 1,
col: 1,
}
}
fn error(&self, msg: &str) -> String {
format!(
"Lexer error at line {}, col {}: {}",
self.line, self.col, msg
)
}
fn peek(&self, offset: usize) -> Option<char> {
self.source.get(self.pos + offset).copied()
}
fn advance(&mut self) -> Option<char> {
if self.pos >= self.source.len() {
return None;
}
let ch = self.source[self.pos];
self.pos += 1;
if ch == '\n' {
self.line += 1;
self.col = 1;
} else {
self.col += 1;
}
Some(ch)
}
fn skip_whitespace(&mut self) {
while let Some(ch) = self.peek(0) {
if ch.is_whitespace() {
self.advance();
} else {
break;
}
}
}
fn skip_comment(&mut self) {
if self.peek(0) == Some('/') && self.peek(1) == Some('/') {
while let Some(ch) = self.peek(0) {
if ch == '\n' {
break;
}
self.advance();
}
}
}
fn read_number(&mut self) -> i32 {
let mut num_str = String::new();
while let Some(ch) = self.peek(0) {
if ch.is_ascii_digit() {
num_str.push(ch);
self.advance();
} else {
break;
}
}
num_str.parse().unwrap_or(0)
}
fn read_identifier(&mut self) -> String {
let mut ident = String::new();
while let Some(ch) = self.peek(0) {
if ch.is_alphanumeric() || ch == '_' {
ident.push(ch);
self.advance();
} else {
break;
}
}
ident
}
fn read_string(&mut self) -> Result<String, String> {
let mut string = String::new();
self.advance(); // Consume the opening quote
while let Some(ch) = self.peek(0) {
if ch == '"' {
self.advance(); // Consume the closing quote
return Ok(string);
} else if ch == '\\' {
self.advance(); // Consume the backslash
if let Some(escaped_char) = self.peek(0) {
string.push(escaped_char);
self.advance();
}
} else {
string.push(ch);
self.advance();
}
}
Err(String::from("Unexpected EOF"))
}
fn read_char(&mut self) -> Result<char, String> {
self.advance(); // Consume the opening quote
if let Some(ch) = self.peek(0) {
self.advance();
if self.peek(0) == Some('\'') {
self.advance();
return Ok(ch);
} else {
Err(String::from("expected closing quote"))
}
} else {
Err(String::from("expected character"))
}
}
pub fn tokenize(&mut self) -> Result<Vec<Token>, String> {
let mut tokens = Vec::new();
loop {
self.skip_whitespace();
self.skip_comment();
if self.pos >= self.source.len() {
break;
}
let line = self.line;
let col = self.col;
let ch = self.peek(0).unwrap();
let token_type = if ch.is_ascii_digit() {
let num = self.read_number();
TokenType::Number(num)
} else if ch == '"' {
let string = self.read_string()?;
TokenType::String(string)
} else if ch == '\'' {
let char = self.read_char()?;
TokenType::Char(char)
} else if ch.is_alphabetic() || ch == '_' {
let ident = self.read_identifier();
match ident.as_str() {
"int" => TokenType::Int,
"if" => TokenType::If,
"else" => TokenType::Else,
"while" => TokenType::While,
"return" => TokenType::Return,
"include" => TokenType::Include,
_ => TokenType::Identifier(ident),
}
} else {
match ch {
':' if self.peek(1) == Some(':') => {
self.advance();
self.advance();
TokenType::Namespace
}
':' => {
self.advance();
TokenType::Colon
}
'=' if self.peek(1) == Some('=') => {
self.advance();
self.advance();
TokenType::Eq
}
'!' if self.peek(1) == Some('=') => {
self.advance();
self.advance();
TokenType::Ne
}
'<' if self.peek(1) == Some('=') => {
self.advance();
self.advance();
TokenType::Le
}
'>' if self.peek(1) == Some('=') => {
self.advance();
self.advance();
TokenType::Ge
}
'+' => {
self.advance();
TokenType::Plus
}
'-' => {
self.advance();
TokenType::Minus
}
'*' => {
self.advance();
TokenType::Star
}
'/' => {
self.advance();
TokenType::Slash
}
'=' => {
self.advance();
TokenType::Assign
}
'<' => {
self.advance();
TokenType::Lt
}
'>' => {
self.advance();
TokenType::Gt
}
'(' => {
self.advance();
TokenType::LParen
}
')' => {
self.advance();
TokenType::RParen
}
'{' => {
self.advance();
TokenType::LBrace
}
'}' => {
self.advance();
TokenType::RBrace
}
';' => {
self.advance();
TokenType::Semicolon
}
',' => {
self.advance();
TokenType::Comma
}
_ => return Err(self.error(&format!("Unexpected character: {}", ch))),
}
};
tokens.push(Token::new(token_type, line, col));
}
tokens.push(Token::new(TokenType::Eof, self.line, self.col));
Ok(tokens)
}
}
+25
View File
@@ -0,0 +1,25 @@
use common::logging::log;
use crate::model::{CompilerError, Program};
use parser::Parser;
pub mod lexer;
pub mod parser;
pub fn generate_ast(input: &str) -> Result<Program, CompilerError> {
log("Tokenising Input...");
let mut lexer = lexer::Lexer::new(&input);
let tokens = lexer.tokenize().map_err(|e| CompilerError::Generic(e))?;
// println!("{tokens:?}");
log(&format!("Parsing {} Tokens...", tokens.len()));
let mut parser = Parser::new(tokens);
let ast = match parser.parse() {
Ok(ast) => ast,
Err(e) => return Err(CompilerError::Generic(e)),
};
Ok(ast)
}
+482
View File
@@ -0,0 +1,482 @@
// ============================================================================
// AST Node Types
// ============================================================================
use crate::model::{
BinaryOperator, Block, ConstExpr, Declaration, Dependency, Expression, Name, Program,
Statement, TypeId, UnaryOperator, Variable,
};
use super::lexer::{Token, TokenType};
// ============================================================================
// Parser
// ============================================================================
pub struct Parser {
tokens: Vec<Token>,
pos: usize,
}
impl Parser {
pub fn new(tokens: Vec<Token>) -> Self {
Self { tokens, pos: 0 }
}
fn error(&self, msg: &str) -> String {
let token = self.current();
format!(
"Parser error at line {}, col {}: {}",
token.line, token.col, msg
)
}
fn current(&self) -> &Token {
self.tokens
.get(self.pos)
.unwrap_or_else(|| self.tokens.last().unwrap())
}
fn peek(&self, offset: usize) -> &Token {
self.tokens
.get(self.pos + offset)
.unwrap_or_else(|| self.tokens.last().unwrap())
}
fn advance(&mut self) -> &Token {
if self.pos < self.tokens.len() - 1 {
self.pos += 1;
}
self.current()
}
fn expect(&mut self, expected: TokenType) -> Result<Token, String> {
let token = self.current().clone();
if std::mem::discriminant(&token.token_type) != std::mem::discriminant(&expected)
{
return Err(self.error(&format!(
"Expected {:?}, got {:?}",
expected, token.token_type
)));
}
self.advance();
Ok(token)
}
pub fn parse(&mut self) -> Result<Program, String> {
let mut declarations = Vec::new();
while !matches!(self.current().token_type, TokenType::Eof) {
declarations.push(self.parse_declaration()?);
}
Ok(Program { declarations })
}
fn parse_declaration(&mut self) -> Result<Declaration, String> {
// check for an import
if let TokenType::Include = self.current().token_type {
self.advance();
let name =
if let TokenType::Identifier(id) = self.current().clone().token_type {
Some(id)
} else {
None
}
.ok_or(String::from("Expected identifier"))?;
self.advance();
self.expect(TokenType::Colon)?;
let path = if let TokenType::String(id) = self.current().clone().token_type {
Some(id)
} else {
None
}
.ok_or(String::from("Expected string literal"))?;
self.advance();
return Ok(Declaration::Dependency(Dependency { name, path }));
}
self.expect(TokenType::Int)?;
let name = match &self.current().token_type {
TokenType::Identifier(s) => s.clone(),
_ => return Err(self.error("Expected identifier")),
};
self.advance();
match &self.current().token_type {
TokenType::LParen => {
// Function declaration
self.advance();
let mut params = Vec::<Variable>::new();
if !matches!(self.current().token_type, TokenType::RParen) {
self.expect(TokenType::Int)?;
match &self.current().token_type {
TokenType::Identifier(s) => {
params.push(Variable {
name: s.clone(),
type_id: TypeId::U32,
});
self.advance();
}
_ => return Err(self.error("Expected parameter name")),
}
while matches!(self.current().token_type, TokenType::Comma) {
self.advance();
self.expect(TokenType::Int)?;
match &self.current().token_type {
TokenType::Identifier(s) => {
params.push(Variable {
name: s.clone(),
type_id: TypeId::U32,
});
self.advance();
}
_ => return Err(self.error("Expected parameter name")),
}
}
}
self.expect(TokenType::RParen)?;
let body = self.parse_block()?;
Ok(Declaration::Function {
name,
params,
body,
return_type: TypeId::U32,
})
}
_ => {
// Variable declaration
let init = if matches!(self.current().token_type, TokenType::Assign) {
self.advance();
if let TokenType::Number(n) = self.current().token_type {
self.advance();
Some(ConstExpr::Number(n))
} else {
return Err(self
.error("Expected constant in global variable declaration"));
}
} else {
None
};
self.expect(TokenType::Semicolon)?;
Ok(Declaration::Variable {
var: Variable {
name,
type_id: TypeId::U32,
},
init,
is_const: false,
})
}
}
}
fn parse_block(&mut self) -> Result<Block, String> {
self.expect(TokenType::LBrace)?;
let mut statements = Vec::new();
while !matches!(self.current().token_type, TokenType::RBrace) {
statements.push(self.parse_statement()?);
}
self.expect(TokenType::RBrace)?;
Ok(statements)
}
fn parse_statement(&mut self) -> Result<Statement, String> {
match &self.current().token_type {
TokenType::LBrace => Ok(Statement::Block(self.parse_block()?)),
TokenType::If => self.parse_if_stmt(),
TokenType::While => self.parse_while_stmt(),
TokenType::Return => self.parse_return_stmt(),
TokenType::Identifier(name) => {
let name = name.clone();
// peek ahead for open paren (func call expr)
if matches!(self.peek(1).token_type, TokenType::LParen) {
let expr = self.parse_expression()?; // a function call expr
self.expect(TokenType::Semicolon)?;
return Ok(Statement::Expression { expr });
}
self.advance(); // advance past identifier
// assignment expression
if matches!(self.current().token_type, TokenType::Assign) {
self.advance();
let expr = self.parse_expression()?;
self.expect(TokenType::Semicolon)?;
Ok(Statement::Assign {
varname: name,
value: expr,
})
}
// var expression
else {
self.expect(TokenType::Semicolon)?;
Ok(Statement::Expression {
expr: Expression::Variable {
name: Name {
name,
namespace: None,
},
expr_type: None,
},
})
}
}
TokenType::Int => {
// Local variable declaration
self.advance();
let name = match &self.current().token_type {
TokenType::Identifier(s) => s.clone(),
_ => return Err(self.error("Expected variable name")),
};
self.advance();
let init = if matches!(self.current().token_type, TokenType::Assign) {
self.advance();
Some(self.parse_expression()?)
} else {
None
};
self.expect(TokenType::Semicolon)?;
// Convert to assignment expression statement
let expr = if let Some(init_expr) = init {
Statement::Assign {
varname: name,
value: init_expr,
}
} else {
Statement::Assign {
varname: name,
value: Expression::Empty,
}
};
Ok(expr)
}
_ => {
let expr = if matches!(self.current().token_type, TokenType::Semicolon) {
Expression::Empty
} else {
self.parse_expression()?
};
self.expect(TokenType::Semicolon)?;
Ok(Statement::Expression { expr })
}
}
}
fn parse_if_stmt(&mut self) -> Result<Statement, String> {
self.expect(TokenType::If)?;
self.expect(TokenType::LParen)?;
let condition = self.parse_expression()?;
self.expect(TokenType::RParen)?;
let then_stmt = self.parse_block()?;
let else_stmt = if matches!(self.current().token_type, TokenType::Else) {
self.advance();
self.parse_block()?
} else {
Vec::new()
};
Ok(Statement::If {
condition,
then_stmt,
else_stmt,
})
}
fn parse_while_stmt(&mut self) -> Result<Statement, String> {
self.expect(TokenType::While)?;
self.expect(TokenType::LParen)?;
let condition = self.parse_expression()?;
self.expect(TokenType::RParen)?;
let body = self.parse_block()?;
Ok(Statement::While { condition, body })
}
fn parse_return_stmt(&mut self) -> Result<Statement, String> {
self.expect(TokenType::Return)?;
let expr = if matches!(self.current().token_type, TokenType::Semicolon) {
None
} else {
Some(self.parse_expression()?)
};
self.expect(TokenType::Semicolon)?;
Ok(Statement::Return(expr))
}
fn parse_expression(&mut self) -> Result<Expression, String> {
self.parse_comparison()
}
fn parse_comparison(&mut self) -> Result<Expression, String> {
let mut expr = self.parse_additive()?;
while let Some(op) = match &self.current().token_type {
TokenType::Eq => Some(BinaryOperator::Eq),
TokenType::Ne => Some(BinaryOperator::Ne),
TokenType::Lt => Some(BinaryOperator::Lt),
TokenType::Gt => Some(BinaryOperator::Gt),
TokenType::Le => Some(BinaryOperator::Le),
TokenType::Ge => Some(BinaryOperator::Ge),
_ => None,
} {
self.advance();
let right = Box::new(self.parse_additive()?);
expr = Expression::Binary {
op,
left: Box::new(expr),
right,
type_id: None,
};
}
Ok(expr)
}
fn parse_additive(&mut self) -> Result<Expression, String> {
let mut expr = self.parse_multiplicative()?;
while let Some(op) = match &self.current().token_type {
TokenType::Plus => Some(BinaryOperator::Add),
TokenType::Minus => Some(BinaryOperator::Sub),
_ => None,
} {
self.advance();
let right = Box::new(self.parse_multiplicative()?);
expr = Expression::Binary {
op,
left: Box::new(expr),
right,
type_id: None,
};
}
Ok(expr)
}
fn parse_multiplicative(&mut self) -> Result<Expression, String> {
let mut expr = self.parse_unary()?;
while let Some(op) = match &self.current().token_type {
TokenType::Star => Some(BinaryOperator::Mul),
TokenType::Slash => Some(BinaryOperator::Div),
_ => None,
} {
self.advance();
let right = Box::new(self.parse_unary()?);
expr = Expression::Binary {
op,
left: Box::new(expr),
right,
type_id: None,
};
}
Ok(expr)
}
fn parse_unary(&mut self) -> Result<Expression, String> {
let op = match &self.current().token_type {
TokenType::Plus => Some(UnaryOperator::Plus),
TokenType::Minus => Some(UnaryOperator::Minus),
_ => None,
};
if let Some(op) = op {
self.advance();
let operand = Box::new(self.parse_unary()?);
return Ok(Expression::Unary {
op,
operand,
type_id: None,
});
}
self.parse_primary()
}
fn parse_primary(&mut self) -> Result<Expression, String> {
match &self.current().token_type.clone() {
TokenType::Number(n) => {
let value = *n;
self.advance();
Ok(Expression::Number {
value: value as isize,
type_id: None,
})
}
TokenType::Identifier(name) => {
let name = name.clone();
self.advance();
if matches!(self.current().token_type, TokenType::LParen) {
// Function call
self.advance();
let mut args = Vec::new();
if !matches!(self.current().token_type, TokenType::RParen) {
args.push(self.parse_expression()?);
while matches!(self.current().token_type, TokenType::Comma) {
self.advance();
args.push(self.parse_expression()?);
}
}
self.expect(TokenType::RParen)?;
Ok(Expression::Call {
name: Name {
name,
namespace: None,
},
args,
type_id: None,
})
} else {
Ok(Expression::Variable {
name: Name {
name,
namespace: None,
},
expr_type: None,
})
}
}
TokenType::LParen => {
self.advance();
let expr = self.parse_expression()?;
self.expect(TokenType::RParen)?;
Ok(expr)
}
_ => Err(self.error(&format!(
"Unexpected token: {:?}",
self.current().token_type
))),
}
}
}
File diff suppressed because it is too large Load Diff
+38
View File
@@ -0,0 +1,38 @@
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
@@ -0,0 +1,987 @@
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()))
}
}
}};
}
@@ -0,0 +1,226 @@
use std::collections::HashMap;
use crate::model::{
BinaryOperator, // You'll need to add this to your imports
CompilerError,
Declaration,
Dependency,
Expression,
Program,
TypeId,
UnaryOperator,
};
pub struct Analyser {
symbol_table: HashMap<String, Declaration>,
}
const NUMERIC_TYPES: &[TypeId] = &[
TypeId::U32,
TypeId::I32,
TypeId::I16,
TypeId::U16,
TypeId::I8,
TypeId::U8,
];
impl Analyser {
pub fn new() -> Self {
Self {
symbol_table: HashMap::new(),
}
}
pub fn analyse(&mut self, ast: Program) -> Result<(), CompilerError> {
// build table of global symbols.
for dec in ast.declarations {
let name = match dec.clone() {
Declaration::Function { name, .. } => name,
Declaration::Variable { var, .. } => var.name,
Declaration::Dependency(Dependency { name, .. }) => name,
};
self.symbol_table.insert(name, dec);
}
Ok(())
}
fn match_type(
actual: TypeId,
expected: Option<TypeId>,
) -> Result<TypeId, CompilerError> {
match expected {
Some(id) => {
if id != actual {
Err(CompilerError::TypeMismatch(id, actual))
} else {
Ok(actual)
}
}
None => Ok(actual),
}
}
fn get_type(
&mut self, // Changed from &self to &mut self since we modify expr
expr: &mut Expression,
expected_type: Option<TypeId>,
) -> Result<TypeId, CompilerError> {
match expr {
// Correct IFF we're expecting a void type
Expression::Empty => Self::match_type(TypeId::Void, expected_type),
// Correct IFF we're expecting a char type
Expression::CharLiteral(_) => Self::match_type(TypeId::Char, expected_type),
// Correct IFF we're expecting a string slice type
Expression::StringLiteral(_) => {
Self::match_type(TypeId::Ptr(Box::new(TypeId::Char)), expected_type)
}
Expression::Variable { name, expr_type } => {
let actual = expr_type.clone().ok_or(CompilerError::UnknownType)?;
Self::match_type(actual, expected_type)
}
Expression::Number { value, type_id } => {
// If we already know the TypeId
if let Some(id) = type_id {
return Self::match_type(id.clone(), expected_type);
}
// If we're expecting a type id, check it's numeric.
// TODO: add checks to make sure it's valid for its size eg u8 cant be
// more than 255
if let Some(expected) = expected_type {
if NUMERIC_TYPES.contains(&expected) {
*type_id = Some(expected.clone());
return Ok(expected);
} else {
return Err(CompilerError::TypeMismatch(expected, TypeId::U32));
}
}
// Default to i32 if no type information is available
*type_id = Some(TypeId::I32);
Ok(TypeId::I32)
}
Expression::Binary {
op,
left,
right,
type_id,
} => {
// For binary operations, both operands should have compatible types
// and the result type depends on the operation
let left_type = self.get_type(left, None)?;
let right_type = self.get_type(right, Some(left_type.clone()))?;
// For numeric operations, result has the same type as operands
if NUMERIC_TYPES.contains(&left_type)
&& NUMERIC_TYPES.contains(&right_type)
{
*type_id = Some(left_type);
Self::match_type(left_type, expected_type)
} else {
Err(CompilerError::TypeMismatch(left_type, right_type))
}
}
Expression::Unary {
op,
operand,
type_id,
} => {
match op {
UnaryOperator::Plus | UnaryOperator::Minus => {
// Unary +/- require numeric operands
let inner_type = self.get_type(operand, None)?;
if NUMERIC_TYPES.contains(&inner_type) {
*type_id = Some(inner_type.clone());
Self::match_type(inner_type, expected_type)
} else {
Err(CompilerError::TypeMismatch(inner_type, TypeId::I32))
}
}
UnaryOperator::Dereference => {
// For dereference (*ptr), the operand must be a pointer
// and the result type is what the pointer points to
let inner_type = self.get_type(operand, None)?;
match inner_type {
TypeId::Ptr(inner) => {
let deref_type = *inner;
*type_id = Some(deref_type.clone());
Self::match_type(deref_type, expected_type)
}
_ => Err(CompilerError::Generic(format!(
"Cannot dereference non-pointer type: {:?}",
inner_type
))),
}
}
UnaryOperator::Reference => {
// For reference (&var), we need to determine what we're taking
// a reference to, then wrap it in a Ptr
// If expected_type is Ptr(T), then operand should have type T
let expected_inner = match expected_type.clone() {
Some(TypeId::Ptr(inner)) => Some(*inner),
_ => None,
};
let inner_type = self.get_type(operand, expected_inner)?;
let ref_type = TypeId::Ptr(Box::new(inner_type));
*type_id = Some(ref_type.clone());
Self::match_type(ref_type, expected_type)
}
}
}
Expression::Call {
name,
args,
type_id,
} => match self.symbol_table.get(&name.name) {
Some(Declaration::Function {
params,
return_type,
..
}) => {
// check that we've given the right number of arguments.
if args.len() != params.len() {
return Err(CompilerError::Generic(format!(
"Function {} expected {} arguments but received {}",
name.name,
params.len(),
args.len()
)));
}
for (arg, param) in args.iter_mut().zip(params.iter()) {
// check that the argument type matches the parameter type.
let provided_type = self.get_type(arg, Some(param.type_id))?;
if provided_type != param.type_id {
return Err(CompilerError::TypeMismatch(
param.type_id,
provided_type,
));
}
}
*type_id = Some(return_type.clone());
Self::match_type(return_type.clone(), expected_type)
}
_ => Err(CompilerError::Generic(format!(
"Function {} not found in symbol table",
name.name
))),
},
}
}
}
+15
View File
@@ -0,0 +1,15 @@
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
@@ -0,0 +1,72 @@
#![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
@@ -0,0 +1,19 @@
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
@@ -0,0 +1,495 @@
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
@@ -0,0 +1,135 @@
#[must_use]
pub fn build(src: &str) -> String {
parse(src).join("\n")
}
#[must_use]
#[expect(clippy::too_many_lines)]
pub fn parse(src: &str) -> Vec<String> {
let stack = "0x10000";
let acc = "acc";
let rga = "rga";
let bpr = "bpr";
let spr = "spr";
let mut instrs = Vec::<String>::new();
// Define symbols
let print_start = "print";
let tokens = lex(src);
let mut idstack = Vec::<u32>::new();
// set up a stack
instrs.push(format!("\tlwi {}, {}", stack, bpr));
instrs.push(format!("\tmov {}, {}", bpr, spr));
// set up the data pointer
instrs.push(format!("{}: \t lwi 0x30000, {}", "main", rga));
for (id, tok) in tokens.iter().enumerate() {
match tok {
BfToken::Inc => {
instrs.push(format!("\tinc {}", acc));
}
BfToken::Dec => {
instrs.push(format!("\tdec {}", acc));
}
BfToken::IncPtr => {
instrs.push(format!("\tstw {}, {}, 0", acc, rga));
instrs.push(format!("\taddi {}, 4, {}", rga, rga));
instrs.push(format!("\tlwd {}, {}, 0", rga, acc));
}
BfToken::DecPtr => {
instrs.push(format!("\tstw {}, {}, 0", acc, rga));
instrs.push(format!("\tsubi {}, 4, {}", rga, rga));
instrs.push(format!("\tlwd {}, {}, 0", rga, acc));
}
BfToken::Out => {
instrs.push(format!("\tpush {}", acc));
instrs.push(format!("\tcall {}", print_start));
instrs.push(format!("\tpop zero"));
}
BfToken::In => {
instrs.push(format!("\tlwd 0x40000, {}, 0", acc));
}
BfToken::Forward => {
let loop_start = format!("loop_start_{}", id);
let loop_end = format!("loop_end_{}", id);
idstack.push(id as u32);
instrs.push(format!("\tcmp {}, zero", acc));
instrs.push(format!("\tjeq {}, zero", loop_end));
instrs.push(format!("{}: \tnop", loop_start));
}
BfToken::Back => {
if let Some(start_id) = idstack.pop() {
let loop_start = format!("loop_start_{}", start_id);
let loop_end = format!("loop_end_{}", start_id);
instrs.push(format!("\tcmp {}, zero", acc));
instrs.push(format!("\tjne {}, zero", loop_start));
instrs.push(format!("{}: \tnop", loop_end));
} else {
eprintln!("Warning: Unmatched ']' at position {}", id);
}
}
}
}
instrs.push("\thlt".to_string());
insert_lib(&mut instrs);
instrs
}
fn insert_lib(instrs: &mut Vec<String>) {
let bpr = "bpr";
let spr = "spr";
let rg0 = "rg0";
let rg1 = "rg1";
let print_start = "print";
let current = "current";
instrs.push(format!("\tdw {}, 0x20000", current));
instrs.push(format!("{}: \tpush {}", print_start, bpr));
instrs.push(format!("\tmov {}, {}", spr, bpr));
instrs.push(format!("\tlwd {}, {}, 8", bpr, rg0));
instrs.push(format!("\tlwd {}, {}, 0", current, rg1));
instrs.push(format!("\tstb {}, {}, 0", rg0, rg1));
instrs.push(format!("\taddi {}, 1, {}", rg1, rg1));
instrs.push(format!("\tstw {}, {}, 0", rg1, current));
instrs.push(format!("\tmov {}, {}", bpr, spr));
instrs.push(format!("\tpop {}", bpr));
instrs.push("\treturn".to_string());
}
enum BfToken {
Inc,
Dec,
IncPtr,
DecPtr,
Out,
In,
Forward,
Back,
}
fn lex(src: &str) -> Vec<BfToken> {
src.chars()
.filter_map(|c| match c {
'+' => Some(BfToken::Inc),
'-' => Some(BfToken::Dec),
'>' => Some(BfToken::IncPtr),
'<' => Some(BfToken::DecPtr),
'.' => Some(BfToken::Out),
',' => Some(BfToken::In),
'[' => Some(BfToken::Forward),
']' => Some(BfToken::Back),
_ => None,
})
.collect()
}
fn _create_symbol(id: u32) -> String {
format!("label_{}", id)
}
+13
View File
@@ -0,0 +1,13 @@
use crate::model::CompilerError;
pub mod brainf;
pub fn build_specialised(ext: &str, data: &str) -> Option<Result<String, CompilerError>> {
match ext {
"bf" => {
let res = brainf::build(data);
Some(Ok(res))
}
_ => None,
}
}
File diff suppressed because it is too large Load Diff
+429
View File
@@ -0,0 +1,429 @@
# 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
@@ -0,0 +1,875 @@
# 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
@@ -0,0 +1,638 @@
# 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
@@ -0,0 +1,4 @@
- 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
@@ -0,0 +1,149 @@
# 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
@@ -0,0 +1,26 @@
# 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
@@ -0,0 +1,25 @@
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,5 +1,6 @@
#![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
@@ -0,0 +1,10 @@
[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
@@ -0,0 +1,200 @@
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
@@ -0,0 +1,589 @@
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
"#
)
}
+7 -3
View File
@@ -16,21 +16,22 @@ 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" }
eframe = { version = "0.31.1" }
egui = "0.31.1" egui = "0.31.1"
dirs = "6.0.0" dirs = "6.0.0"
discord-presence = { version = "1.6.0", optional = true } discord-presence = { version = "2.0.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"]
discord-rpc = ["dep:discord-presence"] discord-rpc = ["dep:discord-presence"]
config = ["dep:toml", "dep:serde"] config = ["dep:toml", "dep:serde"]
# Add support for Android for the fun of it. # Add support for Android for the fun of it. Currently crashes lol.
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
winit = { version = "0.30.11", features = ["android-native-activity"] } winit = { version = "0.30.11", features = ["android-native-activity"] }
# jni = "0.21.1" # jni = "0.21.1"
@@ -38,3 +39,6 @@ winit = { version = "0.30.11", features = ["android-native-activity"] }
[target.'cfg(target_os = "android")'.dependencies.eframe] [target.'cfg(target_os = "android")'.dependencies.eframe]
version = "0.31.1" version = "0.31.1"
features = ["android-native-activity"] features = ["android-native-activity"]
[target.'cfg(not(target_os = "android"))'.dependencies.eframe]
version = "0.31.1"
+53
View File
@@ -0,0 +1,53 @@
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()
}
}
+110 -99
View File
@@ -1,15 +1,12 @@
use std::sync::Arc; use std::sync::Arc;
use std::{ use std::sync::mpsc::{self, Receiver, Sender};
sync::mpsc::{self, Receiver, Sender},
thread,
time::Duration,
};
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::emulator::misc::rpc::{Activity, RpcClient}; use crate::emulator::misc::rpc::{Activity, RpcClient};
use crate::emulator::system::model::StateUpdate;
use crate::emulator::system::{ use crate::emulator::system::{
model::{Command, PersistentState, Running, State}, model::{Command, Running},
processor::Processor, processor::Processor,
}; };
@@ -19,28 +16,37 @@ use common::prelude::*;
#[allow(unused_variables)] #[allow(unused_variables)]
pub fn run_emulator( pub fn run_emulator(
cmd_rx: &Receiver<Command>, cmd_rx: &Receiver<Command>,
state_tx: &Sender<State>, state_tx: &Sender<StateUpdate>,
mut processor: Processor, mut processor: Processor,
rpc_client: Option<&Arc<RpcClient>>, rpc_client: Option<&Arc<RpcClient>>,
) { ) {
println!("INFO: Starting emulator."); println!("INFO: Starting emulator.");
let mut running = Running::Paused; let mut running = Running::Paused;
let mut addr = 0u32; let mut step = 0;
let mut history = Vec::<(u32, Instruction)>::new(); let mut addr;
let mut history = Vec::<(u32, u32)>::with_capacity(32768);
let size = 256; let size = 256;
let memory_view = processor.memory.read_range(addr, size); let record_history = true;
let initial_state = state(&mut processor, running, 0, memory_view, &mut history);
let _ = state_tx.send(initial_state); state_tx
.send(StateUpdate::Running(Running::Paused))
.expect("Failed to send initial state!");
let mut instruction_count = 0; let mut instruction_count = 0;
let mut update = false;
loop { loop {
let cmd = if running == Running::Running { let cmd = if 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) => None, Err(mpsc::TryRecvError::Empty) => {
update = false;
None
}
Err(mpsc::TryRecvError::Disconnected) => break, Err(mpsc::TryRecvError::Disconnected) => break,
} }
} else { } else {
@@ -50,10 +56,15 @@ 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.
@@ -69,9 +80,11 @@ 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 => {
@@ -91,118 +104,116 @@ pub fn run_emulator(
processor.reset(); processor.reset();
} }
Command::Step => { Command::Step(x) => {
step = x;
running = Running::Paused; running = Running::Paused;
// Execute one cycle.
match processor.cycle() {
Ok((addr, instruction)) => {
history.push((addr, instruction));
}
Err(why) => {
let pcx = processor.get(Register::Pcx);
eprintln!(
"Could not decode instruction at {pcx:x}. Reason: {why}"
);
continue;
}
}
instruction_count += 1;
}
Command::Read(new, _size) => {
addr = new;
} }
Command::Write(offset, data) => { Command::Write(offset, data) => {
update = true;
processor.memory.write_range(offset, data); processor.memory.write_range(offset, data);
} }
Command::Interrupt(_interrupt) => { Command::Interrupt(_interrupt) => {
update = true;
todo!("implement interrupts") todo!("implement interrupts")
} }
Command::MemRequest(new, size) if update => {
addr = new;
let _ = state_tx.send(StateUpdate::MemoryView(
processor.memory.read_range(addr, size),
));
}
Command::DisplayRequest if update => {
let _ = state_tx.send(StateUpdate::DisplayView(
processor.display().unwrap_or_else(|_| {
report_err(
state_tx,
"Failed to read display!",
&mut processor,
);
Vec::new()
}),
));
}
Command::StackRequest if update => {
let _ = state_tx.send(StateUpdate::StackView(
processor.get_stack(32).unwrap_or_else(|_| {
report_err(state_tx, "Failed to read stack!", &mut processor);
Vec::new()
}),
));
}
Command::RegisterRequest if update => {
let _ = state_tx.send(StateUpdate::Registers(processor.registers));
}
Command::RunningRequest if update => {
let _ = state_tx.send(StateUpdate::Running(running));
}
Command::HistoryRequest if update => {
let hsc = history.clone();
history.clear();
let _ = state_tx.send(StateUpdate::InstructionHistory(hsc));
}
Command::InstructionCountRequest if update => {
let _ = state_tx.send(StateUpdate::Instructions(instruction_count));
}
Command::WriteBlock(addr, block) => {
processor.memory.write_range(addr, block.to_vec());
}
_ => {}
} }
let memory_view = processor.memory.read_range(addr, size);
let state = state(
&mut processor,
running,
instruction_count,
memory_view,
&mut history,
);
println!("state");
let _ = state_tx.send(state);
} }
if running == Running::Running { if running == Running::Running {
let mut update = false; step += 1;
}
if step > 0 {
step -= 1;
update = true;
// Execute one cycle. // Execute one cycle.
let instruction = match processor.cycle() { let instruction = match processor.cycle() {
Ok(instruction) => instruction, Ok(instruction) => instruction,
Err(why) => { Err(why) => {
let pcx = processor.get(Register::Pcx); let pcx = processor
eprintln!("Could not decode instruction at {pcx:x}. Reason: {why}"); .get(Register::Pcx)
continue; .expect("PCX should never be invalid");
report_err(
state_tx,
&format!(
"Could not decode instruction at {pcx:x}. Reason: {why}"
),
&mut processor,
);
(pcx, Instruction::Nop)
} }
}; };
history.push(instruction); if record_history {
history.push((
instruction.0,
processor
.get(Register::Cir)
.expect("CIR should never be invalid"),
));
}
// let instruction = match Instruction::decode(cpu_lock.get(Register::Cir)) if matches!(instruction, (_, Instruction::Halt)) {
// {};
if matches!(instruction.1, Instruction::Halt) {
running = Running::Halted; running = Running::Halted;
update = true; step = 0;
} }
instruction_count += 1; instruction_count += 1;
// Send state updates every 100 instructions
if instruction_count % 100 == 0 {
update = true;
}
if update {
let memory_view = processor.memory.read_range(addr, size);
let state = state(
&mut processor,
running,
instruction_count,
memory_view,
&mut history,
);
println!("running state");
// println!("state!!! {:?}", state.history);
let _ = state_tx.send(state);
}
} else {
thread::sleep(Duration::from_millis(1));
} }
} }
} }
fn state( fn report_err(state_tx: &Sender<StateUpdate>, why: &str, processor: &mut Processor) {
cpu_lock: &mut Processor, processor
running: Running, .begin_interrupt(Interrupt::HardFault)
instruction_count: usize, .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.");
memory_view: Vec<u8>, let _ = state_tx.send(StateUpdate::Error(why.to_string()));
history: &mut Vec<(u32, Instruction)>,
) -> State {
let hsclone = history.clone();
history.clear();
State {
// TODO: Replace with actual register access from your CPU.
reg_file: cpu_lock.registers,
running,
instructions: instruction_count,
stack_view: cpu_lock.get_stack(32),
memory_view,
display_view: cpu_lock.display(),
error: None,
persistent: PersistentState { history: hsclone },
}
} }
+87 -52
View File
@@ -1,22 +1,42 @@
use std::collections::HashMap; use rustc_hash::FxHashMap;
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) -> u8;
fn write_byte(&mut self, addr: u32, value: u8); fn write_byte(&mut self, addr: u32, value: u8);
fn read_word(&mut self, addr: u32) -> u32; fn read_word(&mut self, addr: u32) -> Result<u32, ProcessorError>;
fn write_word(&mut self, addr: u32, value: u32); fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError>;
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8>;
fn write_range(&mut self, addr: u32, value: Vec<u8>); fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8> {
let mut data = Vec::with_capacity(size as usize);
for i in 0..size {
data.push(self.read_byte(addr + i));
}
data
}
fn write_range(&mut self, addr: u32, value: Vec<u8>) {
for (i, byte) in value.into_iter().enumerate() {
self.write_byte(addr + i as u32, byte);
}
}
fn read_block(&mut self, addr: u32) -> &[u8; 256];
fn write_block(&mut self, addr: u32, data: &[u8; 256]) {
for (i, byte) in data.iter().enumerate() {
self.write_byte(addr + i as u32, *byte);
}
}
} }
pub struct MainStore { pub struct MainStore {
pub data: HashMap<u32, Block>, pub data: FxHashMap<u32, Block>,
} }
pub struct Block { pub type Block = [u8; 256];
data: [u8; 256],
}
impl Default for MainStore { impl Default for MainStore {
fn default() -> Self { fn default() -> Self {
@@ -28,95 +48,110 @@ impl MainStore {
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
data: HashMap::new(), data: FxHashMap::default(),
} }
} }
#[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 self.data.entry(addr).or_insert([0; 256])
.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 self.data.entry(addr).or_insert([0; 256])
.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) -> u8 { 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.data[offset as usize] block[offset as usize]
} }
fn read_word(&mut self, addr: u32) -> u32 { #[inline]
fn read_word(&mut self, addr: u32) -> Result<u32, ProcessorError> {
if !addr.is_multiple_of(4) {
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;
println!("reading word from {block_addr:x?} + {offset}"); let block = self.block(block_addr);
Ok(u32::from_be_bytes(
let block = self.mut_block(block_addr); block[offset..=offset + 3]
let mut bytes = [0; 4]; .try_into()
bytes[0] = block.data[offset as usize]; .expect("Failed to read word!"),
bytes[1] = block.data[(offset + 1) as usize]; ))
bytes[2] = block.data[(offset + 2) as usize];
bytes[3] = block.data[(offset + 3) as usize];
u32::from_be_bytes(bytes)
} }
#[inline]
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8> { 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));
} }
// println!("reading {data:?} from {addr:x?}");
data data
} }
#[inline]
fn write_byte(&mut self, addr: u32, value: u8) { 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.data[offset as usize] = value; block[offset as usize] = value;
} }
fn write_word(&mut self, addr: u32, value: u32) { #[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_addr, offset) = Self::segment_addr(addr);
let block = self.mut_block(block_addr); let block = self.mut_block(block_addr);
block.data[offset as usize] = (value >> 24) as u8; block[offset as usize..=(offset + 3) as usize]
block.data[(offset + 1) as usize] = (value >> 16) as u8; .copy_from_slice(&value.to_be_bytes());
block.data[(offset + 2) as usize] = (value >> 8) as u8; Ok(())
block.data[(offset + 3) as usize] = value as u8;
} }
#[inline]
fn write_range(&mut self, addr: u32, value: Vec<u8>) { fn write_range(&mut self, addr: u32, value: Vec<u8>) {
// println!("writing {value:?} to {addr:x?}"); let mut current_block_addr = addr / 256;
let mut current_block = self.mut_block(current_block_addr);
for (i, byte) in value.into_iter().enumerate() { let mut offset = addr % 256;
let (block_addr, offset) = Self::segment_addr(addr + i as u32); for byte in value {
let block = self.mut_block(block_addr); current_block[offset as usize] = byte;
block.data[offset as usize] = byte; offset += 1;
if offset >= 256 {
offset = 0;
current_block_addr += 1;
current_block = self.mut_block(current_block_addr);
}
} }
} }
#[inline]
fn read_block(&mut self, addr: u32) -> &[u8; 256] {
let (block_addr, _) = Self::segment_addr(addr);
self.block(block_addr)
}
#[inline]
fn write_block(&mut self, addr: u32, data: &[u8; 256]) {
let (block_addr, _) = Self::segment_addr(addr);
let _ = self.data.insert(block_addr, *data);
}
} }
+1
View File
@@ -1,3 +1,4 @@
pub mod cache;
pub mod emulator; pub mod emulator;
pub mod memory; pub mod memory;
pub mod model; pub mod model;
+142 -57
View File
@@ -1,3 +1,5 @@
use std::sync::mpsc::{self, Receiver, Sender};
use common::prelude::*; use common::prelude::*;
#[derive(PartialEq, Eq, Debug, Clone, Copy)] #[derive(PartialEq, Eq, Debug, Clone, Copy)]
@@ -16,15 +18,143 @@ pub trait IODevice: Send + Sync {
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]
pub enum Command { pub enum Command {
// set emulator state.
Start, Start,
Stop, Stop,
Step, Step(usize),
Reset(usize), Reset(usize),
Interrupt(Interrupt), Interrupt(Interrupt),
// Performs direct read/write operations on the emulator's memory.
Read(Address, u32),
Write(Address, Vec<u8>), Write(Address, Vec<u8>),
WriteBlock(Address, Box<[u8; 256]>),
// request emulator state.
MemRequest(Address, u32),
DisplayRequest,
StackRequest,
RegisterRequest,
RunningRequest,
HistoryRequest,
InstructionCountRequest,
}
#[derive(Debug)]
pub enum ProcessorError {
InvalidInstruction(u32),
InvalidRegister(u8),
BadMemoryAccess(u32),
}
impl std::error::Error for ProcessorError {}
impl std::fmt::Display for ProcessorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidInstruction(instruction) => {
write!(f, "Invalid instruction: {instruction}")
}
Self::InvalidRegister(register) => {
write!(f, "Invalid register: {register}")
}
Self::BadMemoryAccess(address) => {
write!(f, "Bad memory access: {address}")
}
}
}
}
pub struct State {
pub state_receiver: Receiver<StateUpdate>,
pub cmd_sender: Sender<Command>,
// Processor state
pub reg_file: RegFile,
pub running: Running,
pub instructions: usize,
// Memory access views
pub stack_view: Vec<u8>,
pub memory_view: Vec<u8>,
pub display_view: Vec<u8>,
pub error_log: Vec<String>,
pub instruction_history: Vec<(u32, u32)>,
}
impl State {
#[must_use]
pub fn new(sender: Sender<Command>, receiver: Receiver<StateUpdate>) -> Self {
Self {
state_receiver: receiver,
cmd_sender: sender,
reg_file: RegFile::default(),
running: Running::Paused,
instructions: 0,
stack_view: vec![],
memory_view: vec![],
display_view: vec![],
error_log: vec![],
instruction_history: vec![],
}
}
pub fn send(&mut self, cmd: Command) {
if let Err(e) = self.cmd_sender.send(cmd) {
self.error_log.push(e.to_string());
}
}
pub fn update(&mut self) -> Result<(), mpsc::TryRecvError> {
while let Ok(update) = self.state_receiver.try_recv() {
match update {
StateUpdate::Registers(reg_file) => self.reg_file = reg_file,
StateUpdate::Running(running) => self.running = running,
StateUpdate::Instructions(instructions) => {
self.instructions = instructions;
}
StateUpdate::StackView(stack_view) => self.stack_view = stack_view,
StateUpdate::MemoryView(memory_view) => self.memory_view = memory_view,
StateUpdate::DisplayView(display_view) => {
self.display_view = display_view;
}
StateUpdate::Error(err_state) => self.error_log.push(err_state),
StateUpdate::InstructionHistory(history) => {
self.instruction_history.extend(history);
}
}
if self.error_log.len() > 256 {
self.error_log.drain(0..self.error_log.len() - 256);
}
if self.instruction_history.len() > 1024 {
self.instruction_history
.drain(0..self.instruction_history.len() - 1024);
}
}
if let Err(e) = self.state_receiver.try_recv() {
match e {
mpsc::TryRecvError::Empty => {}
mpsc::TryRecvError::Disconnected => {
return Err(e);
}
}
}
Ok(())
}
}
pub enum StateUpdate {
Registers(RegFile),
Running(Running),
Instructions(usize),
StackView(Vec<u8>),
MemoryView(Vec<u8>),
DisplayView(Vec<u8>),
Error(String),
InstructionHistory(Vec<(u32, u32)>),
} }
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
@@ -127,8 +257,8 @@ impl RegFile {
self.pcx = 0; self.pcx = 0;
} }
pub fn reg(&mut self, reg: Register) -> &mut u32 { pub const fn reg(&mut self, reg: Register) -> Result<&mut u32, ProcessorError> {
match reg { Ok(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,
@@ -156,13 +286,12 @@ 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,
_ => panic!("Invalid register."), _ => return Err(ProcessorError::InvalidRegister(Register::Null as u8)),
} })
} }
#[must_use] pub const fn get(&self, reg: Register) -> Result<u32, ProcessorError> {
pub fn get(&self, reg: Register) -> u32 { Ok(match reg {
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,
@@ -191,51 +320,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,
_ => panic!("Invalid register."), _ => return Err(ProcessorError::InvalidRegister(Register::Null as u8)),
} })
}
}
pub struct State {
pub reg_file: RegFile,
pub running: Running,
pub instructions: usize,
// Memory access views
pub stack_view: Vec<u8>,
pub memory_view: Vec<u8>,
pub display_view: Vec<u8>,
pub error: Option<String>,
pub persistent: PersistentState,
}
impl Default for State {
fn default() -> Self {
Self {
reg_file: RegFile::default(),
running: Running::Paused,
instructions: 0,
stack_view: vec![],
memory_view: vec![],
display_view: vec![],
persistent: PersistentState::default(),
error: None,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct PersistentState {
pub history: Vec<(u32, Instruction)>,
}
impl PersistentState {
pub fn update(&mut self, new_state: &Self) {
self.history.extend(new_state.history.clone());
if self.history.len() > 1024 {
let len = self.history.len() - 1024;
self.history.drain(..len);
}
} }
} }
+155 -125
View File
@@ -4,13 +4,12 @@ use std::{
}; };
use crate::emulator::system::{ use crate::emulator::system::{
cache::Cache,
memory::MemoryUnit, memory::MemoryUnit,
model::{IODevice, RegFile}, model::{IODevice, ProcessorError, RegFile},
}; };
use common::instructions::{ use common::instructions::{Instruction, Interrupt, Register};
Instruction, Interrupt, Register, errors::InstructionDecodeError,
};
pub struct Processor { pub struct Processor {
pub memory: Box<dyn MemoryUnit>, pub memory: Box<dyn MemoryUnit>,
@@ -18,14 +17,10 @@ pub struct Processor {
pub halted: bool, pub halted: bool,
pub io_devices: Vec<Arc<dyn IODevice>>, pub io_devices: Vec<Arc<dyn IODevice>>,
pub dustbin: u32, pub void: u32,
pub cache: Cache,
} }
fn log(message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}");
}
#[allow(clippy::needless_pass_by_ref_mut)]
impl Processor { impl Processor {
#[must_use] #[must_use]
pub fn new(memory: Box<dyn MemoryUnit>, io_devices: Vec<Arc<dyn IODevice>>) -> Self { pub fn new(memory: Box<dyn MemoryUnit>, io_devices: Vec<Arc<dyn IODevice>>) -> Self {
@@ -34,7 +29,8 @@ impl Processor {
registers: RegFile::default(), registers: RegFile::default(),
halted: false, halted: false,
io_devices, io_devices,
dustbin: 0, void: 0,
cache: Cache::new(),
} }
} }
@@ -48,49 +44,60 @@ impl Processor {
self.memory.reset(); self.memory.reset();
} }
pub fn cycle(&mut self) -> Result<(u32, Instruction), InstructionDecodeError> { pub fn cycle(&mut self) -> Result<(u32, Instruction), ProcessorError> {
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::Mar) = val; *self.reg(Register::Cir)? = encoded;
// Decode and execute the instruction. let decoded = if let Some(val) = self.cache.lookup_instruction(addr) {
let instruction = Instruction::decode(val)?; val
} else {
let decoded = Instruction::decode(encoded)
.map_err(|_| ProcessorError::InvalidInstruction(encoded))?;
self.cache.insert(addr, decoded);
decoded
};
log(&instruction.to_string()); decoded.execute(self)?;
Ok((addr, decoded))
instruction.execute(self);
Ok((addr, instruction))
} }
fn fetch(&self) -> u32 { const fn fetch(&self) -> Result<u32, ProcessorError> {
self.get(Register::Pcx) self.get(Register::Pcx)
} }
#[must_use] pub const fn get(&self, reg: Register) -> Result<u32, ProcessorError> {
pub fn get(&self, reg: Register) -> u32 {
self.registers.get(reg) self.registers.get(reg)
} }
pub fn reg(&mut self, reg: Register) -> &mut u32 { pub const fn reg(&mut self, reg: Register) -> Result<&mut u32, ProcessorError> {
match reg { match reg {
Register::Zero => &mut self.dustbin, Register::Zero => Ok(&mut self.void),
_ => self.registers.reg(reg), _ => self.registers.reg(reg),
} }
} }
pub fn display(&mut self) -> Vec<u8> { pub fn display(&mut self) -> Result<Vec<u8>, ProcessorError> {
self.memory.read_range(0x20000, 2000) Ok(self.memory.read_range(0x20000, 2000))
} }
pub fn cmp(&mut self, a: u32, b: u32) { pub fn cmp(&mut self, a: u32, b: u32) {
@@ -99,59 +106,80 @@ impl Processor {
self.set_flag(Flag::LessThan, a < b); self.set_flag(Flag::LessThan, a < b);
} }
// stack operations
pub fn push(&mut self, value: u32) {
let stack_ptr = self.get(Register::Spr);
*self.reg(Register::Spr) += 4;
self.memory.write_word(stack_ptr, value);
}
pub fn pop(&mut self) -> u32 {
*self.reg(Register::Spr) -= 4;
self.memory.read_word(self.get(Register::Spr))
}
// 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.reg(Register::Sts) |= flag as u32; *self
.reg(Register::Sts)
.expect("STS should never be invalid") |= flag as u32;
} else { } else {
*self.reg(Register::Sts) &= !(flag as u32); *self
.reg(Register::Sts)
.expect("STS should never be invalid") &= !(flag as u32);
} }
} }
fn get_flag(&self, flag: Flag) -> bool { fn get_flag(&self, flag: Flag) -> Result<bool, ProcessorError> {
self.get(Register::Sts) & (flag as u32) != 0 Ok(self.get(Register::Sts)? & (flag as u32) != 0)
} }
fn advance(&mut self) { fn advance(&mut self) -> Result<(), ProcessorError> {
// increment PCX // increment PCX
*self.reg(Register::Pcx) += 4; *self.reg(Register::Pcx)? += 4;
Ok(())
} }
fn jump(&mut self, reg: Register, offset: u16) { fn jump(&mut self, reg: Register, offset: u16) -> Result<(), ProcessorError> {
*self.reg(Register::Pcx) = self.get(reg) + u32::from(offset); *self.reg(Register::Pcx)? = self.get(reg)? + u32::from(offset);
Ok(())
} }
fn begin_interrupt(&mut self, _int: Interrupt) { pub fn begin_interrupt(
// first we get the address of the interrupt descriptor table. &mut self,
todo!(); interrupt: Interrupt,
) -> Result<(), ProcessorError> {
let idt = self.get(Register::Idr)?;
let addr = self
.memory
.read_word(idt + u32::from(interrupt.as_u8()) * 4)?;
println!("INFO: Interrupt {interrupt:?} addr: {addr}");
self.push(self.get(Register::Pcx)?)?;
*self.reg(Register::Pcx)? = addr;
Ok(())
} }
fn end_interrupt(&mut self) { fn push(&mut self, val: u32) -> Result<(), ProcessorError> {
todo!(); *self.reg(Register::Spr)? -= 4;
let reg = *self.reg(Register::Spr)?;
self.memory.write_word(reg, val)
} }
pub fn get_stack(&mut self, n: u32) -> Vec<u8> { fn pop(&mut self) -> Result<u32, ProcessorError> {
let addr = self.get(Register::Spr); 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
#[allow(clippy::needless_pass_by_ref_mut)]
fn end_interrupt(&mut self) -> Result<(), ProcessorError> {
let ret = self.pop()?;
*self.reg(Register::Ret)? = ret;
*self.reg(Register::Pcx)? = ret;
Ok(())
}
pub fn get_stack(&mut self, n: u32) -> Result<Vec<u8>, ProcessorError> {
let addr = self.get(Register::Spr)?;
let size = n * 4; let size = n * 4;
// returns the stack // returns the stack
self.memory.read_range( Ok(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
) ))
} }
} }
@@ -170,38 +198,40 @@ enum Flag {
} }
trait Executable { trait Executable {
fn execute(self, cpu: &mut Processor); fn execute(self, cpu: &mut Processor) -> Result<(), ProcessorError>;
} }
impl Executable for Instruction { impl Executable for Instruction {
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn execute(self, cpu: &mut Processor) { fn execute(self, cpu: &mut Processor) -> Result<(), ProcessorError> {
match self { match self {
// 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.read_byte(cpu.get(a.r1) + u32::from(a.immediate)), cpu.memory
.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.read_byte(cpu.get(a.r1) + u32::from(a.immediate)), cpu.memory
.read_byte(cpu.get(a.r1)? + u32::from(a.immediate)),
)); ));
} }
@@ -210,31 +240,36 @@ 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.reg(a.r2)? = cpu
cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate)) >> 16; .memory
.read_word(cpu.get(a.r1)? + u32::from(a.immediate))?
>> 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.read_word(cpu.get(a.r1) + u32::from(a.immediate)) >> 16, cpu.memory
.read_word(cpu.get(a.r1)? + u32::from(a.immediate))?
>> 16,
); );
} }
// 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.reg(a.r2)? = cpu
cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate)); .memory
.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,
); );
} }
@@ -242,149 +277,143 @@ impl Executable for Instruction {
// 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 cpu.memory.write_word(
.write_word(cpu.get(a.r2) + u32::from(a.immediate), cpu.get(a.r1)); cpu.get(a.r2)? + u32::from(a.immediate),
cpu.get(a.r1)?,
)?;
} }
// Loads a 16-bit literal value into reg, setting the bottom 16 bits of the // 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) => {
let regval = cpu.get(a.sr2); *cpu.reg(a.dr)? = shl(cpu.get(a.sr1)?, a.shamt + cpu.get(a.sr2)? as u8);
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) => {
let regval = cpu.get(a.sr2); *cpu.reg(a.dr)? = shr(cpu.get(a.sr1)?, a.shamt + cpu.get(a.sr2)? as u8);
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.
@@ -392,12 +421,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.
@@ -411,6 +440,7 @@ impl Executable for Instruction {
todo!() todo!()
} }
} }
Ok(())
} }
} }
+299 -131
View File
@@ -13,22 +13,32 @@ fn test_nop_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
let initial_state = cpu.registers; let initial_state = cpu.registers;
Instruction::Nop.execute(&mut cpu); Instruction::Nop.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!( assert_eq!(
cpu.registers.get(Register::Rg0), cpu.registers
initial_state.get(Register::Rg0) .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.get(Register::Acc), cpu.registers
initial_state.get(Register::Acc) .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) = 0x1234_5678; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1234_5678;
let mov_instr = Instruction::Mov(RTypeArgs::new( let mov_instr = Instruction::Mov(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -37,14 +47,19 @@ fn test_mov_instruction() {
None, None,
)); ));
mov_instr.execute(&mut cpu); mov_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg2), 0x1234_5678); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0x1234_5678
);
} }
#[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) = 0x0000_00FF; *cpu.reg(Register::Rg1).expect("Failed to get 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),
@@ -53,8 +68,13 @@ fn test_mov_signed_instruction() {
None, None,
)); ));
mov_signed_instr.execute(&mut cpu); mov_signed_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg2), 0xFFFF_FFFF); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0xFFFF_FFFF
);
} }
#[test] #[test]
@@ -62,7 +82,7 @@ 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.write_byte(addr, 0xAB);
*cpu.reg(Register::Rg1) = addr - 4; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr - 4;
let load_byte_instr = Instruction::LoadByte(ITypeArgs::new( let load_byte_instr = Instruction::LoadByte(ITypeArgs::new(
4, 4,
@@ -70,8 +90,13 @@ fn test_load_byte_instruction() {
Some(Register::Rg2), Some(Register::Rg2),
)); ));
load_byte_instr.execute(&mut cpu); load_byte_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg2), 0x0000_00AB); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0x0000_00AB
);
} }
#[test] #[test]
@@ -79,7 +104,7 @@ 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.write_byte(addr, 0xFF);
*cpu.reg(Register::Rg1) = addr; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
let load_byte_signed_instr = Instruction::LoadByteSigned(ITypeArgs::new( let load_byte_signed_instr = Instruction::LoadByteSigned(ITypeArgs::new(
0, 0,
@@ -87,16 +112,23 @@ fn test_load_byte_signed_instruction() {
Some(Register::Rg2), Some(Register::Rg2),
)); ));
load_byte_signed_instr.execute(&mut cpu); load_byte_signed_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg2), 0xFFFF_FFFF); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0xFFFF_FFFF
);
} }
#[test] #[test]
fn test_load_halfword_instruction() { fn test_load_halfword_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
let addr = 0x100; let addr = 0x100;
cpu.memory.write_word(addr, 0x1234_5678); cpu.memory
*cpu.reg(Register::Rg1) = addr; .write_word(addr, 0x1234_5678)
.expect("Failed to write word to memory");
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
let load_halfword_instr = Instruction::LoadHalfword(ITypeArgs::new( let load_halfword_instr = Instruction::LoadHalfword(ITypeArgs::new(
0, 0,
@@ -104,16 +136,23 @@ fn test_load_halfword_instruction() {
Some(Register::Rg2), Some(Register::Rg2),
)); ));
load_halfword_instr.execute(&mut cpu); load_halfword_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg2), 0x0000_1234); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0x0000_1234
);
} }
#[test] #[test]
fn test_load_word_instruction() { fn test_load_word_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
let addr = 0x100; let addr = 0x100;
cpu.memory.write_word(addr, 0x1234_5678); cpu.memory
*cpu.reg(Register::Rg1) = addr; .write_word(addr, 0x1234_5678)
.expect("Failed to write word to memory");
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
let load_word_instr = Instruction::LoadWord(ITypeArgs::new( let load_word_instr = Instruction::LoadWord(ITypeArgs::new(
0, 0,
@@ -121,16 +160,21 @@ fn test_load_word_instruction() {
Some(Register::Rg2), Some(Register::Rg2),
)); ));
load_word_instr.execute(&mut cpu); load_word_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg2), 0x1234_5678); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0x1234_5678
);
} }
#[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) = addr; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
*cpu.reg(Register::Rg2) = 0xAB; *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0xAB;
let store_byte_instr = Instruction::StoreByte(ITypeArgs::new( let store_byte_instr = Instruction::StoreByte(ITypeArgs::new(
0, 0,
@@ -138,7 +182,9 @@ fn test_store_byte_instruction() {
Some(Register::Rg1), Some(Register::Rg1),
)); ));
store_byte_instr.execute(&mut cpu); store_byte_instr.execute(&mut cpu).expect(
"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), 0xAB);
} }
@@ -146,8 +192,8 @@ fn test_store_byte_instruction() {
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) = addr; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
*cpu.reg(Register::Rg2) = 0x1234_5678; *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0x1234_5678;
let store_word_instr = Instruction::StoreWord(ITypeArgs::new( let store_word_instr = Instruction::StoreWord(ITypeArgs::new(
0, 0,
@@ -155,15 +201,17 @@ fn test_store_word_instruction() {
Some(Register::Rg1), Some(Register::Rg1),
)); ));
store_word_instr.execute(&mut cpu); store_word_instr.execute(&mut cpu).expect(
assert_eq!(cpu.memory.read_word(addr), 0x1234_5678); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.memory.read_word(addr).expect("Emulator was slain by losing the game while attempting to execute instruction"), 0x1234_5678);
} }
#[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) = 15; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 15;
*cpu.reg(Register::Rg2) = 25; *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 25;
let add_instr = Instruction::Add(RTypeArgs::new( let add_instr = Instruction::Add(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -172,15 +220,20 @@ fn test_add_instruction() {
None, None,
)); ));
add_instr.execute(&mut cpu); add_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg3), 40); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
40
);
} }
#[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) = 50; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 50;
*cpu.reg(Register::Rg2) = 20; *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 20;
let sub_instr = Instruction::Sub(RTypeArgs::new( let sub_instr = Instruction::Sub(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -189,15 +242,20 @@ fn test_sub_instruction() {
None, None,
)); ));
sub_instr.execute(&mut cpu); sub_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg3), 30); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
30
);
} }
#[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) = 0b1100; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010; *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
let and_instr = Instruction::And(RTypeArgs::new( let and_instr = Instruction::And(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -206,15 +264,20 @@ fn test_and_instruction() {
None, None,
)); ));
and_instr.execute(&mut cpu); and_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg3), 0b1000); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
0b1000
);
} }
#[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) = 0b1100; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010; *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
let or_instr = Instruction::Or(RTypeArgs::new( let or_instr = Instruction::Or(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -223,15 +286,20 @@ fn test_or_instruction() {
None, None,
)); ));
or_instr.execute(&mut cpu); or_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg3), 0b1110); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
0b1110
);
} }
#[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) = 0b1100; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010; *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
let xor_instr = Instruction::Xor(RTypeArgs::new( let xor_instr = Instruction::Xor(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -240,14 +308,19 @@ fn test_xor_instruction() {
None, None,
)); ));
xor_instr.execute(&mut cpu); xor_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg3), 0b0110); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
0b0110
);
} }
#[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) = 0x0F0F_0F0F; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x0F0F_0F0F;
let not_instr = Instruction::Not(RTypeArgs::new( let not_instr = Instruction::Not(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -256,15 +329,20 @@ fn test_not_instruction() {
None, None,
)); ));
not_instr.execute(&mut cpu); not_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg2), 0xF0F0_F0F0); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
0xF0F0_F0F0
);
} }
#[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) = 42; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 42;
*cpu.reg(Register::Rg2) = 42; *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 42;
let cmp_instr = Instruction::Compare(RTypeArgs::new( let cmp_instr = Instruction::Compare(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -273,18 +351,26 @@ fn test_compare_equal() {
None, None,
)); ));
cmp_instr.execute(&mut cpu); cmp_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert!(cpu.get_flag(Flag::Equal)); assert!(cpu.get_flag(Flag::Equal).expect("Failed to get flag Equal"));
assert!(!cpu.get_flag(Flag::GreaterThan)); assert!(
assert!(!cpu.get_flag(Flag::LessThan)); !cpu.get_flag(Flag::GreaterThan)
.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) = 50; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 50;
*cpu.reg(Register::Rg2) = 30; *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 30;
let cmp_instr = Instruction::Compare(RTypeArgs::new( let cmp_instr = Instruction::Compare(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -293,18 +379,26 @@ fn test_compare_greater_than() {
None, None,
)); ));
cmp_instr.execute(&mut cpu); cmp_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert!(!cpu.get_flag(Flag::Equal)); assert!(!cpu.get_flag(Flag::Equal).expect("Failed to get flag Equal"));
assert!(cpu.get_flag(Flag::GreaterThan)); assert!(
assert!(!cpu.get_flag(Flag::LessThan)); cpu.get_flag(Flag::GreaterThan)
.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) = 20; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 20;
*cpu.reg(Register::Rg2) = 30; *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 30;
let cmp_instr = Instruction::Compare(RTypeArgs::new( let cmp_instr = Instruction::Compare(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -313,85 +407,114 @@ fn test_compare_less_than() {
None, None,
)); ));
cmp_instr.execute(&mut cpu); cmp_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert!(!cpu.get_flag(Flag::Equal)); assert!(!cpu.get_flag(Flag::Equal).expect("Failed to get flag Equal"));
assert!(!cpu.get_flag(Flag::GreaterThan)); assert!(
assert!(cpu.get_flag(Flag::LessThan)); !cpu.get_flag(Flag::GreaterThan)
.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) = 42; *cpu.reg(Register::Rg1).expect("Failed to get 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));
inc_instr.execute(&mut cpu); inc_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg1), 43); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
43
);
} }
#[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) = 42; *cpu.reg(Register::Rg1).expect("Failed to get 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));
dec_instr.execute(&mut cpu); dec_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg1), 41); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
41
);
} }
#[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) = 0b1010; *cpu.reg(Register::Rg1).expect("Failed to get 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),
None, Some(Register::Rg1),
Some(2), Some(2),
)); ));
shl_instr.execute(&mut cpu); shl_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg1), 0b10_1000); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
0b10_1000
);
} }
#[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) = 0b10_1000; *cpu.reg(Register::Rg1).expect("Failed to get 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),
None, Some(Register::Rg1),
Some(2), Some(2),
)); ));
shr_instr.execute(&mut cpu); shr_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg1), 0b1010); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
0b1010
);
} }
#[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) = 0b1010; // *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1010;
*cpu.reg(Register::Rg2) = 3;
let shl_instr = Instruction::ShiftLeft(RTypeArgs::new( // let shl_instr =
Some(Register::Rg1), // Instruction::ShiftLeft(RTypeArgs::new(Some(Register::Rg1), None, None,
Some(Register::Rg2), // Some(3)));
None,
None,
));
shl_instr.execute(&mut cpu); // shl_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg1), 0b101_0000); // "Emulator was slain by losing the game while attempting to execute
} // instruction", );
// assert_eq!(
// cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
// 0b101_0000
// );
// }
#[test] #[test]
fn test_load_lower_immediate() { fn test_load_lower_immediate() {
@@ -403,14 +526,19 @@ fn test_load_lower_immediate() {
None, None,
)); ));
lli_instr.execute(&mut cpu); lli_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg1), 0x0000_1234); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
0x0000_1234
);
} }
#[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) = 0x0000_5678; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x0000_5678;
let lui_instr = Instruction::LoadUpperImmediate(ITypeArgs::new( let lui_instr = Instruction::LoadUpperImmediate(ITypeArgs::new(
0x1234, 0x1234,
@@ -418,48 +546,71 @@ fn test_load_upper_immediate() {
None, None,
)); ));
lui_instr.execute(&mut cpu); lui_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg1), 0x1234_5678); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
0x1234_5678
);
} }
#[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) = 0x1000; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1000;
let initial_pc = cpu.get(Register::Pcx); let initial_pc = cpu.get(Register::Pcx).expect("Failed to 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); jump_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Pcx), 0x1100); "Emulator was slain by losing the game while attempting to execute instruction",
assert_ne!(cpu.get(Register::Pcx), initial_pc); );
assert_eq!(
cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
0x1100
);
assert_ne!(
cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
initial_pc
);
} }
#[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) = 0x1000; *cpu.reg(Register::Rg1).expect("Failed to get 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));
jump_eq_instr.execute(&mut cpu); jump_eq_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Pcx), 0x1100); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
0x1100
);
} }
#[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) = 0x1000; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1000;
let initial_pc = cpu.get(Register::Pcx); let initial_pc = cpu.get(Register::Pcx).expect("Failed to 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));
jump_eq_instr.execute(&mut cpu); jump_eq_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Pcx), initial_pc); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
initial_pc
);
} }
#[test] #[test]
@@ -467,15 +618,17 @@ fn test_halt_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
assert!(!cpu.halted); assert!(!cpu.halted);
Instruction::Halt.execute(&mut cpu); Instruction::Halt.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert!(cpu.halted); assert!(cpu.halted);
} }
#[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) = 0b1100; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010; *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
let nand_instr = Instruction::Nand(RTypeArgs::new( let nand_instr = Instruction::Nand(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -484,15 +637,20 @@ fn test_nand_instruction() {
None, None,
)); ));
nand_instr.execute(&mut cpu); nand_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg3), !0b1000); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
!0b1000
);
} }
#[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) = 0b1100; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010; *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
let nor_instr = Instruction::Nor(RTypeArgs::new( let nor_instr = Instruction::Nor(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -501,15 +659,20 @@ fn test_nor_instruction() {
None, None,
)); ));
nor_instr.execute(&mut cpu); nor_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg3), !0b1110); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
!0b1110
);
} }
#[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) = 0b1100; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
*cpu.reg(Register::Rg2) = 0b1010; *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;
let xnor_instr = Instruction::Xnor(RTypeArgs::new( let xnor_instr = Instruction::Xnor(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
@@ -518,6 +681,11 @@ fn test_xnor_instruction() {
None, None,
)); ));
xnor_instr.execute(&mut cpu); xnor_instr.execute(&mut cpu).expect(
assert_eq!(cpu.get(Register::Rg3), !0b0110); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(
cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
!0b0110
);
} }
+72 -28
View File
@@ -1,5 +1,3 @@
use std::sync::mpsc::Sender;
use crate::emulator::{ use crate::emulator::{
system::model::{Command, Running, State}, system::model::{Command, Running, State},
ui::interface::Component, ui::interface::Component,
@@ -9,19 +7,27 @@ use common::{instructions::Register, prelude::Instruction};
pub struct ControlPanel { pub struct ControlPanel {
visible: bool, visible: bool,
sender: Sender<Command>, step_amount_input: String,
step_amount: usize,
} }
impl ControlPanel { impl ControlPanel {
#[must_use] #[allow(clippy::must_use_candidate)]
pub const fn new(sender: Sender<Command>) -> Self { pub fn new() -> Self {
Self { Self {
visible: false, visible: false,
sender, step_amount_input: String::from("1"),
step_amount: 1,
} }
} }
} }
impl Default for ControlPanel {
fn default() -> Self {
Self::new()
}
}
impl Component for ControlPanel { impl Component for ControlPanel {
fn category(&self) -> super::interface::Category { fn category(&self) -> super::interface::Category {
super::interface::Category::Control super::interface::Category::Control
@@ -47,46 +53,76 @@ impl Component for ControlPanel {
.clicked() .clicked()
{ {
if state.running == Running::Running { if state.running == Running::Running {
self.sender.send(Command::Stop).unwrap_or_else(|_| { state.cmd_sender.send(Command::Stop).unwrap_or_else(|_| {
state.error = Some("Failed to send command".to_string()); state.error_log.push("Failed to send command".to_string());
}); });
} else { } else {
self.sender.send(Command::Start).unwrap_or_else(|_| { state.cmd_sender.send(Command::Start).unwrap_or_else(|_| {
state.error = Some("Failed to send command".to_string()); state.error_log.push("Failed to send command".to_string());
}); });
} }
} }
// Step // Step
if ui.button("Step").clicked() { if ui.button("Step").clicked() {
self.sender.send(Command::Step).unwrap_or_else(|_| { state
state.error = Some("Failed to send command".to_string()); .cmd_sender
}); .send(Command::Step(self.step_amount))
.unwrap_or_else(|_| {
state.error_log.push("Failed to send command".to_string());
});
} }
// Resets the emulator and all attached devices // Resets the emulator and all attached devices
if ui.button("Reset All").clicked() { if ui.button("Reset All").clicked() {
self.sender.send(Command::Reset(0)).unwrap_or_else(|_| { state
state.error = Some("Failed to send command".to_string()); .cmd_sender
}); .send(Command::Reset(0))
.unwrap_or_else(|_| {
state.error_log.push("Failed to send command".to_string());
});
} }
// Resets the emulator and all attached devices // Resets the emulator and all attached devices
if ui.button("Clear Registers").clicked() { if ui.button("Clear Registers").clicked() {
self.sender.send(Command::Reset(1)).unwrap_or_else(|_| { state
state.error = Some("Failed to send command".to_string()); .cmd_sender
}); .send(Command::Reset(1))
.unwrap_or_else(|_| {
state.error_log.push("Failed to send command".to_string());
});
} }
// Resets the emulator and all attached devices // Resets the emulator and all attached devices
if ui.button("Clear RAM").clicked() { if ui.button("Clear RAM").clicked() {
self.sender.send(Command::Reset(2)).unwrap_or_else(|_| { state
state.error = Some("Failed to send command".to_string()); .cmd_sender
}); .send(Command::Reset(2))
.unwrap_or_else(|_| {
state.error_log.push("Failed to send command".to_string());
});
} }
ui.separator(); ui.separator();
state.send(Command::RegisterRequest);
state.send(Command::RunningRequest);
state.send(Command::InstructionCountRequest);
if ui
.text_edit_singleline(&mut self.step_amount_input)
.changed()
{
self.step_amount = if let Ok(amount) = self.step_amount_input.parse() {
amount
} else {
state
.error_log
.push("Unable to parse step amount".to_string());
1
}
}
// Status info // Status info
ui.label(format!( ui.label(format!(
"Status: {}", "Status: {}",
@@ -97,17 +133,25 @@ impl Component for ControlPanel {
} }
)); ));
let pcx = state.reg_file.get(Register::Pcx); let pcx = state
.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(state.reg_file.get(Register::Cir)) let instruction = Instruction::decode(
.map_or_else( state
|_| "Invalid Instruction".to_string(), .reg_file
|instruction| instruction.to_string(), .get(Register::Cir)
); .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}"));
}); });
+3 -1
View File
@@ -1,5 +1,5 @@
use crate::emulator::{ use crate::emulator::{
system::model::State, system::model::{Command, State},
ui::interface::{Category, Component}, ui::interface::{Category, Component},
}; };
@@ -40,6 +40,8 @@ impl Component for Display {
} }
fn render(&mut self, state: &mut State, ui: &mut egui::Ui, _ctx: &egui::Context) { fn render(&mut self, state: &mut State, ui: &mut egui::Ui, _ctx: &egui::Context) {
state.send(Command::DisplayRequest);
let display: Vec<u8> = state.display_view.clone(); let display: Vec<u8> = state.display_view.clone();
let font_id = FontId::monospace(12.0); let font_id = FontId::monospace(12.0);
+108 -156
View File
@@ -3,7 +3,6 @@ use std::{
ffi::OsStr, ffi::OsStr,
fs, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::mpsc::Sender,
}; };
use common::prelude::Instruction; use common::prelude::Instruction;
@@ -19,6 +18,7 @@ use crate::emulator::{
use assembler::prelude::*; use assembler::prelude::*;
#[derive(Default)]
pub struct Editor { pub struct Editor {
// editor state // editor state
path: Option<PathBuf>, path: Option<PathBuf>,
@@ -41,7 +41,6 @@ pub struct Editor {
// other // other
visible: bool, visible: bool,
sender: Sender<Command>,
error: Option<String>, error: Option<String>,
} }
@@ -94,14 +93,13 @@ impl Component for Editor {
impl Editor { impl Editor {
#[must_use] #[must_use]
pub const fn new(sender: Sender<Command>) -> Self { pub const fn new() -> Self {
Self { Self {
path: None, path: None,
text: String::new(), text: String::new(),
buffer: String::new(), buffer: String::new(),
output: Vec::new(), output: Vec::new(),
unsaved: true, unsaved: true,
sender,
cursor_col: 1, cursor_col: 1,
cursor_line: 1, cursor_line: 1,
visible: false, visible: false,
@@ -119,10 +117,7 @@ impl Editor {
.file_name() .file_name()
.unwrap_or_else(|| OsStr::new("Unnamed!")) .unwrap_or_else(|| OsStr::new("Unnamed!"))
.to_str() .to_str()
.map_or_else( .unwrap_or_else(|| unreachable!("File name should be valid UTF-8."));
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
} }
"Unnamed!" "Unnamed!"
} }
@@ -131,12 +126,9 @@ impl Editor {
if let Some(path) = &self.path { if let Some(path) = &self.path {
return path return path
.extension() .extension()
.map_or_else(|| OsStr::new("Unknown!"), |ext| ext) .unwrap_or_else(|| OsStr::new("Unknown!"))
.to_str() .to_str()
.map_or_else( .unwrap_or_else(|| unreachable!("File name should be valid UTF-8."));
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
} }
"Unknown!" "Unknown!"
} }
@@ -199,38 +191,6 @@ impl Editor {
) )
}); });
// if let Some(path) = FileDialog::new()
// .add_filter("Assembly Files or Binaries", &["dsa", "dsb"])
// .add_filter("all", &["*"])
// .set_directory(&work_dir)
// .pick_file()
// {
// match path.extension().and_then(|ext| ext.to_str()) {
// Some("dsb") => {
// let contents = match std::fs::read(&path) {
// Ok(contents) => contents,
// Err(why) => {
// self.error = Some(format!("Failed to read file: {why}"));
// return;
// }
// };
// self.path = Some(path.clone());
// self.output = contents;
// self.unsaved = false;
// self.text = String::from("Loaded Binary File!");
// self.buffer = self.text.clone();
// self.unsaved = false;
// }
// _ => {
// if let Ok(contents) = std::fs::read_to_string(&path) {
// self.path = Some(path.clone());
// self.text.clone_from(&contents);
// self.buffer = contents;
// self.unsaved = false;
// }
// }
// }
if self.save_file_dialog.is_some() { if self.save_file_dialog.is_some() {
// TODO: Flash an error stating you can only have one menu open at once. // TODO: Flash an error stating you can only have one menu open at once.
self.save_file_dialog = None; self.save_file_dialog = None;
@@ -252,119 +212,92 @@ impl Editor {
fn handle_file_dialogs(&mut self, ctx: &egui::Context) { fn handle_file_dialogs(&mut self, ctx: &egui::Context) {
// Handle open dialog // Handle open dialog
if let Some(dialog) = &mut self.open_file_dialog { if let Some(dialog) = &mut self.open_file_dialog
if dialog.show(ctx).selected() { && dialog.show(ctx).selected()
if let Some(file) = dialog.path() { {
// check if the file is a binary file if let Some(file) = dialog.path() {
if file.extension().is_some_and(|ext| ext == "dsb") { // check if the file is a binary file
match std::fs::read(file) { if file.extension().is_some_and(|ext| ext == "dsb") {
Ok(content) => { match std::fs::read(file) {
let mut res = String::new(); Ok(content) => {
for (i, b) in content.iter().enumerate() { let mut res = String::new();
_ = write!(res, "{b:02x}"); for (i, b) in content.iter().enumerate() {
if i % 4 == 3 { _ = write!(res, "{b:02x}");
res.push('\n'); if i % 4 == 3 {
} res.push('\n');
}
self.text = res.clone();
self.buffer = res;
self.path = Some(file.to_path_buf());
self.unsaved = false;
self.error = None;
}
Err(e) => {
self.error = Some(format!("Failed to read file: {e}"));
}
}
} else {
match std::fs::read_to_string(file) {
Ok(content) => {
self.text = content.clone();
self.buffer = content;
self.path = Some(file.to_path_buf());
self.unsaved = false;
self.error = None;
}
Err(e) => {
self.error = Some(format!("Failed to read file: {e}"));
}
}
}
}
self.open_file_dialog = None;
}
}
// Handle save dialog
if let Some(dialog) = &mut self.save_file_dialog {
if dialog.show(ctx).selected() {
if let Some(file) = dialog.path() {
self.buffer = self.text.clone();
let content = if file.extension().is_some_and(|ext| ext == "dsb") {
let mut res = Vec::new();
for line in self.text.lines() {
for line in line.split_whitespace() {
match u32::from_str_radix(line, 16) {
Ok(num) => res.push(num),
Err(e) => {
self.error =
Some(format!("Failed to parse file: {e}"));
return;
}
} }
} }
} self.text = res.clone();
res.into_iter() self.buffer = res;
.flat_map(u32::to_be_bytes)
.collect::<Vec<u8>>()
} else {
self.text.clone().as_bytes().to_vec()
};
match std::fs::write(file, content) {
Ok(()) => {
self.path = Some(file.to_path_buf()); self.path = Some(file.to_path_buf());
self.unsaved = false; self.unsaved = false;
self.error = None; self.error = None;
} }
Err(e) => { Err(e) => {
self.error = Some(format!("Failed to save file: {e}")); self.error = Some(format!("Failed to read file: {e}"));
}
}
} else {
match std::fs::read_to_string(file) {
Ok(content) => {
self.text = content.clone();
self.buffer = content;
self.path = Some(file.to_path_buf());
self.unsaved = false;
self.error = None;
}
Err(e) => {
self.error = Some(format!("Failed to read file: {e}"));
} }
} }
} }
self.save_file_dialog = None;
} }
self.open_file_dialog = None;
}
// Handle save dialog
if let Some(dialog) = &mut self.save_file_dialog
&& dialog.show(ctx).selected()
{
if let Some(file) = dialog.path() {
self.buffer = self.text.clone();
let content = if file.extension().is_some_and(|ext| ext == "dsb") {
let mut res = Vec::new();
for line in self.text.lines() {
for line in line.split_whitespace() {
match u32::from_str_radix(line, 16) {
Ok(num) => res.push(num),
Err(e) => {
self.error =
Some(format!("Failed to parse file: {e}"));
return;
}
}
}
}
res.into_iter()
.flat_map(u32::to_be_bytes)
.collect::<Vec<u8>>()
} else {
self.text.clone().as_bytes().to_vec()
};
match std::fs::write(file, content) {
Ok(()) => {
self.path = Some(file.to_path_buf());
self.unsaved = false;
self.error = None;
}
Err(e) => {
self.error = Some(format!("Failed to save file: {e}"));
}
}
}
self.save_file_dialog = None;
} }
} }
// fn open(&mut self) {
// let work_dir = std::env::current_dir().unwrap_or_else(|_| {
// dirs::home_dir().expect(
// "Couldn't get your current working directory or your home directory.",
// )
// });
// if let Some(path) = FileDialog::new()
// .add_filter("Assembly Files or Binaries", &["dsa", "dsb"])
// .add_filter("all", &["*"])
// .set_directory(&work_dir)
// .pick_file()
// {
// if let Ok(contents) = std::fs::read_to_string(&path) {
// self.path = Some(path.clone());
// self.text.clone_from(&contents);
// self.buffer = contents;
// self.unsaved = false;
// }
// std::env::set_current_dir(
// path.parent().expect("A file should be in a directory!"),
// )
// .expect("ERROR: Failed to set current working directory.");
// }
// }
fn render_output(&self, _state: &mut State, ui: &mut Ui, _ctx: &Context) { fn render_output(&self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
// Output area with synchronized scrolling // Output area with synchronized scrolling
egui::ScrollArea::vertical() egui::ScrollArea::vertical()
@@ -454,25 +387,20 @@ 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" => Some(Syntax::new("dsa")), "dsa" => Syntax::dsa(),
_ => None, "dsc" => Syntax::dsc(),
_ => Syntax::default(),
}; };
let ed = CodeEditor::default() let mut editor = 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::dsa()) .with_syntax(syntax)
.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);
} }
@@ -512,6 +440,29 @@ 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;
@@ -526,7 +477,7 @@ impl Editor {
} }
} }
fn render_toolbar(&mut self, _state: &mut State, ui: &mut Ui, ctx: &Context) { fn render_toolbar(&mut self, state: &State, ui: &mut Ui, ctx: &Context) {
self.handle_file_dialogs(ctx); self.handle_file_dialogs(ctx);
ui.horizontal(|ui| { ui.horizontal(|ui| {
@@ -567,7 +518,8 @@ impl Editor {
Some("Can't load program at invalid offset!".to_string()); Some("Can't load program at invalid offset!".to_string());
} }
self.sender state
.cmd_sender
.send(Command::Write(self.load_offset, self.output.clone())) .send(Command::Write(self.load_offset, self.output.clone()))
.unwrap_or_else(|_| { .unwrap_or_else(|_| {
self.error = Some("Failed to send command".to_string()); self.error = Some("Failed to send command".to_string());
+13 -4
View File
@@ -1,6 +1,10 @@
use common::prelude::Instruction;
use egui::{Context, Ui}; use egui::{Context, Ui};
use crate::emulator::{system::model::State, ui::interface::Component}; use crate::emulator::{
system::model::{Command, State},
ui::interface::Component,
};
pub struct History { pub struct History {
visible: bool, visible: bool,
@@ -20,11 +24,13 @@ impl Component for History {
} }
fn render(&mut self, state: &mut State, ui: &mut Ui, _ctx: &Context) { fn render(&mut self, state: &mut State, ui: &mut Ui, _ctx: &Context) {
state.send(Command::HistoryRequest);
egui::ScrollArea::vertical() egui::ScrollArea::vertical()
.id_salt("output_scroll") .id_salt("output_scroll")
.max_width(400.0) .max_width(400.0)
.show(ui, |ui| { .show(ui, |ui| {
if state.persistent.history.is_empty() { if state.instruction_history.is_empty() {
ui.label( ui.label(
egui::RichText::new("No output data") egui::RichText::new("No output data")
.font(egui::FontId::monospace(12.0)) .font(egui::FontId::monospace(12.0))
@@ -40,7 +46,7 @@ impl Component for History {
.show(ui, |ui| { .show(ui, |ui| {
// Process bytes in chunks of 4 // Process bytes in chunks of 4
for (idx, instruction) in for (idx, instruction) in
state.persistent.history.iter().enumerate() state.instruction_history.iter().enumerate()
{ {
ui.label(format!("{idx}: ")); ui.label(format!("{idx}: "));
@@ -52,8 +58,11 @@ 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(instruction.1.to_string()) egui::RichText::new(decoded.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)),
); );
+6 -18
View File
@@ -1,4 +1,4 @@
use crate::emulator::system::model::{Command, PersistentState, Running, State}; use crate::emulator::system::model::{Command, Running, State, StateUpdate};
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{Receiver, Sender};
pub trait Component { pub trait Component {
@@ -34,21 +34,15 @@ impl Category {
} }
pub struct EmulatorUI { pub struct EmulatorUI {
pub sender: Sender<Command>,
pub receiver: Receiver<State>,
pub state: State, pub state: State,
pub persistent: PersistentState,
pub components: Vec<Box<dyn Component>>, pub components: Vec<Box<dyn Component>>,
} }
impl EmulatorUI { impl EmulatorUI {
#[must_use] #[must_use]
pub fn new(sender: Sender<Command>, receiver: Receiver<State>) -> Self { pub fn new(sender: Sender<Command>, receiver: Receiver<StateUpdate>) -> Self {
Self { Self {
sender, state: State::new(sender, receiver),
receiver,
state: State::default(),
persistent: PersistentState::default(),
components: vec![], components: vec![],
} }
} }
@@ -56,19 +50,13 @@ impl EmulatorUI {
pub fn add_component(&mut self, component: Box<dyn Component>) { pub fn add_component(&mut self, component: Box<dyn Component>) {
self.components.push(component); self.components.push(component);
} }
fn update_state(&mut self) {
while let Ok(state) = self.receiver.try_recv() {
self.state = state;
self.persistent.update(&self.state.persistent);
self.state.persistent = self.persistent.clone();
}
}
} }
impl eframe::App for EmulatorUI { impl eframe::App for EmulatorUI {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
self.update_state(); if let Err(e) = self.state.update() {
self.state.error_log.push(e.to_string());
}
if self.state.running == Running::Running { if self.state.running == Running::Running {
ctx.request_repaint(); ctx.request_repaint();
+291
View File
@@ -0,0 +1,291 @@
use std::{
ffi::OsStr,
path::{Path, PathBuf},
};
use common::prelude::Instruction;
use egui::{Context, Ui};
use egui_file::FileDialog;
use crate::emulator::{
system::model::{Command, State},
ui::interface::Component,
};
#[derive(Default)]
pub struct Loader {
path: Option<PathBuf>,
output: Vec<u8>,
load_offset: u32,
offset_str: String,
// file dialogs
open_file_dialog: Option<FileDialog>,
// other
visible: bool,
error: Option<String>,
}
impl Component for Loader {
fn name(&self) -> &'static str {
"Loader"
}
fn visible(&mut self) -> &mut bool {
&mut self.visible
}
fn category(&self) -> super::interface::Category {
super::interface::Category::Programming
}
fn render(&mut self, state: &mut State, ui: &mut Ui, ctx: &Context) {
ui.vertical(|ui| {
self.render_toolbar(state, ui, ctx);
ui.add_space(4.0); // Add some spacing instead of just a separator
ui.separator();
egui::ScrollArea::vertical()
.auto_shrink([false; 2])
.max_height(ui.available_height() - 100.0)
.show(ui, |ui| {
self.render_output(state, ui, ctx);
});
self.render_bottom_bar(state, ui, ctx);
});
}
}
impl Loader {
#[must_use]
pub const fn new() -> Self {
Self {
path: None,
output: Vec::new(),
visible: false,
load_offset: 0,
offset_str: String::new(),
error: None,
open_file_dialog: None,
}
}
fn filename(&self) -> &str {
if let Some(path) = &self.path {
return path
.file_name()
.unwrap_or_else(|| OsStr::new("Unnamed!"))
.to_str()
.unwrap_or_else(|| unreachable!("File name should be valid UTF-8."));
}
"Unnamed!"
}
fn open(&mut self) {
let work_dir = std::env::current_dir().unwrap_or_else(|_| {
dirs::home_dir().expect(
"Couldn't get your current working directory or your home directory.",
)
});
if self.open_file_dialog.is_some() {
// TODO: Flash an error stating you can only have one menu open at once.
self.open_file_dialog = None;
}
if self.open_file_dialog.is_none() {
if let Some(p) = &self.path {
let path = p.parent().map(Path::to_path_buf);
let mut dialog = FileDialog::open_file(path);
dialog.open();
self.open_file_dialog = Some(dialog);
} else {
let mut dialog = FileDialog::open_file(Some(work_dir));
dialog.open();
self.open_file_dialog = Some(dialog);
}
}
}
fn handle_file_dialogs(&mut self, ctx: &egui::Context) {
// Handle open dialog
if let Some(dialog) = &mut self.open_file_dialog
&& dialog.show(ctx).selected()
{
if let Some(file) = dialog.path() {
// check if the file is a binary file
if file.extension().is_some_and(|ext| ext == "dsb") {
match std::fs::read(file) {
Ok(content) => {
self.output = content;
self.error = None;
}
Err(e) => {
self.error = Some(format!("Failed to read file: {e}"));
}
}
}
}
self.open_file_dialog = None;
}
}
fn render_output(&self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
// Output area with synchronized scrolling
egui::ScrollArea::vertical()
.id_salt("output_scroll")
.max_width(400.0)
.show(ui, |ui| {
if self.output.is_empty() {
ui.label(
egui::RichText::new("No output data")
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::GRAY),
);
return;
}
egui::Grid::new("output_grid")
.spacing([5.0, 2.0]) // Horizontal and vertical spacing
.num_columns(4)
.striped(false)
.show(ui, |ui| {
// Process bytes in chunks of 4
for (line_num, chunk) in self.output.chunks(4).enumerate() {
let address = line_num * 4;
// Convert chunk to u32 (little-endian)
let mut bytes = [0u8; 4];
for (i, &byte) in chunk.iter().enumerate() {
if i < 4 {
bytes[i] = byte;
}
}
let value = u32::from_be_bytes(bytes);
// Address column
ui.with_layout(
egui::Layout::left_to_right(egui::Align::Center),
|ui| {
ui.set_min_width(80.0);
let style = ui.style_mut();
style.visuals.widgets.inactive.bg_fill =
egui::Color32::from_gray(30);
ui.label(
egui::RichText::new(format!("0x{address:04X}"))
.font(egui::FontId::monospace(12.0)),
);
},
);
// Individual bytes column
let byte_str = chunk
.iter()
.map(|b| format!("{b:02X}"))
.collect::<Vec<_>>()
.join(" ");
ui.label(
egui::RichText::new(format!("{byte_str:<11}"))
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(200, 200, 255)),
);
// Hex column
ui.label(
egui::RichText::new(format!("0x{value:08X}"))
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(255, 200, 200)),
);
// Instruction column
let instruction = Instruction::decode(value).map_or_else(
|_| format!("{value:10}"),
|instruction| instruction.to_string(),
);
ui.label(
egui::RichText::new(instruction)
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(200, 255, 200)),
);
ui.end_row();
}
});
});
}
fn render_bottom_bar(&self, _state: &mut State, ui: &mut Ui, _ctx: &Context) {
ui.horizontal(|ui| {
// error display
ui.label(
egui::RichText::new(self.error.clone().unwrap_or_default())
.color(egui::Color32::RED),
);
});
}
fn render_toolbar(&mut self, state: &State, ui: &mut Ui, ctx: &Context) {
self.handle_file_dialogs(ctx);
ui.horizontal(|ui| {
ui.label(format!("Filename: {}", self.filename()));
});
ui.horizontal(|ui| {
ui.spacing_mut().button_padding = egui::vec2(8.0, 4.0);
ui.spacing_mut().item_spacing.x = 6.0;
// Opens a file
if ui.button("Open").clicked() {
self.open();
}
// Loads the generated binary into the assembler at the provided offset
if ui.button("Load").clicked() {
if self.error.is_some() {
self.error =
Some("Can't load program at invalid offset!".to_string());
}
state
.cmd_sender
.send(Command::Write(self.load_offset, self.output.clone()))
.unwrap_or_else(|_| {
self.error = Some("Failed to send command".to_string());
});
}
// Entry widget to enter a load offset
if ui.text_edit_singleline(&mut self.offset_str).changed() {
if let Some(offset) = parse_address(&self.offset_str) {
self.load_offset = offset;
self.error = None;
} else {
self.error = Some("Invalid offset".to_string());
}
}
});
}
}
fn parse_address(address: &str) -> Option<u32> {
address.strip_prefix("0x").map_or_else(
|| {
address.strip_prefix("0b").map_or_else(
|| {
address.strip_prefix("0o").map_or_else(
|| address.parse::<u32>().ok(),
|oct| u32::from_str_radix(oct, 8).ok(),
)
},
|bin| u32::from_str_radix(bin, 2).ok(),
)
},
|hex| u32::from_str_radix(hex, 16).ok(),
)
}
+24 -20
View File
@@ -1,4 +1,4 @@
use std::{num::ParseIntError, sync::mpsc::Sender}; use std::num::ParseIntError;
use common::prelude::Instruction; use common::prelude::Instruction;
@@ -7,23 +7,22 @@ use crate::emulator::{
ui::interface::Component, ui::interface::Component,
}; };
#[derive(Default)]
pub struct MemoryInspector { pub struct MemoryInspector {
view_size: u32, view_size: u32,
view_addr: u32, view_addr: u32,
visible: bool, visible: bool,
addr_input: String, addr_input: String,
sender: Sender<Command>,
} }
impl MemoryInspector { impl MemoryInspector {
#[must_use] #[must_use]
pub const fn new(sender: Sender<Command>) -> Self { pub const fn new() -> Self {
Self { Self {
view_size: 256, view_size: 256,
view_addr: 0, view_addr: 0,
visible: false, visible: false,
addr_input: String::new(), addr_input: String::new(),
sender,
} }
} }
} }
@@ -63,28 +62,26 @@ impl Component for MemoryInspector {
let search_clicked = ui.button("🔍 Search").clicked(); let search_clicked = ui.button("🔍 Search").clicked();
// Handle Enter key in text field // Handle Enter key in text field
let enter_pressed = let enter_pressed = address_response.lost_focus()
address_response.lost_focus() && ctx.input(|i| i.key_pressed(egui::Key::Enter)); && ctx.input(|i| i.key_pressed(egui::Key::Enter));
if search_clicked || enter_pressed { if search_clicked || enter_pressed {
if let Ok(new) = parse_address(&self.addr_input) { if let Ok(new) = parse_address(&self.addr_input) {
self.view_addr = new; self.view_addr = new;
if let Err(why) = self.sender.send(Command::Read(new, self.view_size)) {
panic!(
"Error sending message across threads -- cannot be recovered: {why}"
)
}
} else { } else {
state.error = Some("Invalid address".to_string()); state.error_log.push("Invalid address".to_string());
} }
} }
let _ = state
.cmd_sender
.send(Command::MemRequest(self.view_addr, self.view_size));
ui.label("(hex or decimal)"); ui.label("(hex or decimal)");
}); });
// Show input error if any // Show input error if any
if let Some(error) = &state.error { if let Some(error) = state.error_log.last() {
ui.colored_label(egui::Color32::RED, format!("Error: {error}")); ui.colored_label(egui::Color32::RED, format!("Error: {error}"));
} }
@@ -113,9 +110,12 @@ impl Component for MemoryInspector {
ui.end_row(); ui.end_row();
// Memory data (8 bytes per row) // Memory data (8 bytes per row)
for (row, chunk) in (0u32..).zip(state.memory_view.chunks(4)) { for (row, chunk) in (0u32..).zip(state.memory_view.chunks(4))
{
let row_address = self.view_addr + (row * 4); let row_address = self.view_addr + (row * 4);
ui.monospace(format!("0x{row_address:08X} ({row_address})")); ui.monospace(format!(
"0x{row_address:08X} ({row_address})"
));
for &byte in chunk { for &byte in chunk {
ui.monospace(format!("{byte:02X}")); ui.monospace(format!("{byte:02X}"));
} }
@@ -126,12 +126,16 @@ impl Component for MemoryInspector {
} }
// combine all 4 bytes in the chunk into a u32 // combine all 4 bytes in the chunk into a u32
let combined = chunk let combined = chunk.iter().fold(0u32, |acc, &byte| {
.iter() (acc << 8) | u32::from(byte)
.fold(0u32, |acc, &byte| (acc << 8) | u32::from(byte)); });
ui.monospace(format!("{combined}")); ui.monospace(format!("{combined}"));
ui.monospace(format!("{}", Instruction::decode(combined).unwrap_or(Instruction::Nop))); ui.monospace(format!(
"{}",
Instruction::decode(combined)
.unwrap_or(Instruction::Nop)
));
ui.end_row(); ui.end_row();
} }
+1
View File
@@ -3,6 +3,7 @@ pub mod display;
pub mod editor; pub mod editor;
pub mod history; pub mod history;
pub mod interface; pub mod interface;
pub mod loader;
pub mod memory_inspector; pub mod memory_inspector;
pub mod menu; pub mod menu;
pub mod stack_inspector; pub mod stack_inspector;
+9 -5
View File
@@ -1,4 +1,7 @@
use crate::emulator::{system::model::State, ui::interface::Component}; use crate::emulator::{
system::model::{Command, State},
ui::interface::Component,
};
use common::instructions::Register; use common::instructions::Register;
@@ -33,6 +36,8 @@ impl Component for StackInspector {
} }
fn render(&mut self, state: &mut State, ui: &mut egui::Ui, _ctx: &egui::Context) { fn render(&mut self, state: &mut State, ui: &mut egui::Ui, _ctx: &egui::Context) {
state.send(Command::StackRequest);
ui.vertical(|ui| { ui.vertical(|ui| {
ui.heading("Stack Inspector"); ui.heading("Stack Inspector");
egui::ScrollArea::vertical() egui::ScrollArea::vertical()
@@ -46,7 +51,6 @@ 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()
{ {
@@ -54,9 +58,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, i*4,
state.reg_file.get(Register::Spr) - i as u32 * 4 state.reg_file.get(Register::Spr).expect("SPR should never be invalid") + i as u32 * 4
)); ));
ui.label(format!("0x{value:08X} ({value})")); ui.label(format!("0x{value:08X} ({value})"));
ui.end_row(); ui.end_row();
+10 -7
View File
@@ -30,7 +30,7 @@ use crate::emulator::{
system::{ system::{
emulator::run_emulator, emulator::run_emulator,
memory::MainStore, memory::MainStore,
model::{Command, State}, model::{Command, StateUpdate},
processor::Processor, processor::Processor,
}, },
ui::{ ui::{
@@ -86,7 +86,7 @@ pub fn android_main(app: AndroidApp) -> Result<(), Box<dyn std::error::Error>> {
pub fn setup_emulator( pub fn setup_emulator(
cmd_receiver: Receiver<Command>, cmd_receiver: Receiver<Command>,
state_sender: Sender<State>, state_sender: Sender<StateUpdate>,
rpc_client: Option<Arc<RpcClient>>, rpc_client: Option<Arc<RpcClient>>,
) { ) {
let main_store = MainStore::new(); let main_store = MainStore::new();
@@ -101,22 +101,22 @@ pub fn setup_emulator(
#[must_use] #[must_use]
pub fn setup_ui( pub fn setup_ui(
cmd_sender: Sender<Command>, cmd_sender: Sender<Command>,
state_reciever: Receiver<State>, state_reciever: Receiver<StateUpdate>,
) -> EmulatorUI { ) -> EmulatorUI {
let mut ui = EmulatorUI::new(cmd_sender.clone(), state_reciever); let mut ui = EmulatorUI::new(cmd_sender, state_reciever);
// Create UI modules. // Create UI modules.
let control_unit = ControlPanel::new(cmd_sender.clone()); let control_unit = ControlPanel::new();
ui.add_component(Box::new(control_unit)); ui.add_component(Box::new(control_unit));
let mem_inspector = MemoryInspector::new(cmd_sender.clone()); let mem_inspector = MemoryInspector::new();
ui.add_component(Box::new(mem_inspector)); ui.add_component(Box::new(mem_inspector));
let stack_inspector = StackInspector::new(); let stack_inspector = StackInspector::new();
ui.add_component(Box::new(stack_inspector)); ui.add_component(Box::new(stack_inspector));
let editor = Editor::new(cmd_sender); let editor = Editor::new();
ui.add_component(Box::new(editor)); ui.add_component(Box::new(editor));
let display = Display::new(); let display = Display::new();
@@ -125,5 +125,8 @@ pub fn setup_ui(
let history = emulator::ui::history::History::new(); let history = emulator::ui::history::History::new();
ui.add_component(Box::new(history)); ui.add_component(Box::new(history));
let loader = emulator::ui::loader::Loader::new();
ui.add_component(Box::new(loader));
ui ui
} }
-279
View File
@@ -1,279 +0,0 @@
```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"));
}
// ...
}
```
+36 -18
View File
@@ -2,10 +2,38 @@
// 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/print.dsa" include print "./lib/io/print.dsa"
// "print hello world" // "print hello world"
db program: "++++++[>++++++++++++<-]>.>++++++++++[>++++++++++<-]>+.+++++++..+++.>++++[>+++++++++++<-]>.<+++[>----<-]>.<<<<<+++[>+++++<-]>.>>.+++.------.--------.>>+." db program: "++++++++++++++++++++++++++++++++++++++++++++
>++++++++++++++++++++++++++++++++
>++++++++++++++++
>
>+
<<
[
>>
>
>++++++++++
<<
[->+>-[>+>>]>[+[-<+>]>+>>]<<<<<<]
>[<+>-]
>[-]
>>
>++++++++++
<
[->-[>+>>]>[+[-<+>]>+>>]<<<<<]
>[-]
>>[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]
<[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]
<<<++++++++++++++++++++++++++++++++++++++++++++++++.[-]
<<<<<<<.>.
>>[>>+<<-]
>[>+<<+>-]
>[<+>-]
<<<-
]
<<++..."
db error: "Invalid Instruction!" db error: "Invalid Instruction!"
dw stack: 0x10000 dw stack: 0x10000
@@ -20,6 +48,7 @@ _init_stack:
start: start:
// load the start of the program into rg0 // load the start of the program into rg0
lwi program, rg0 lwi program, rg0
lwi data, rg1
// rg0 is our instruction pointer // rg0 is our instruction pointer
// rg1 is our data pointer // rg1 is our data pointer
@@ -40,13 +69,6 @@ loop_start:
// load the current instruction into rg3 // load the current instruction into rg3
ldb rg0, rg3 ldb rg0, rg3
// pusha 2
// push rg3
// call print::print_byte
// pop zero
// popa 2
// switch on the instruction // switch on the instruction
// all cases will return to either loop_start or loop_end // all cases will return to either loop_start or loop_end
cmp rg3, rg8 cmp rg3, rg8
@@ -68,19 +90,15 @@ loop_start:
cmp rg3, zero cmp rg3, zero
jeq end jeq end
// if we get here, we don't know what the instruction is // if we get here, we don't know what the instruction is
lwi error, rg0 lwi error, rg2
push rg0
call print::print
pop zero
end:
lwi error, rg2
pusha 2 pusha 2
push rg2 push rg2
call print::print call print::print
pop zero pop zero
popa 2 popa 2
end:
hlt hlt
loop_end: loop_end:
@@ -110,7 +128,7 @@ inc_ptr:
// ------------------------------------------ // ------------------------------------------
// decrement the pointer // decrement the pointer
dec_ptr: dec_ptr:
stw rg1, rg2 stw rg2, rg1
subi rg1, 4 subi rg1, 4
ldw rg1, rg2 ldw rg1, rg2
jmp loop_end jmp loop_end
Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
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;
}
+44
View File
@@ -0,0 +1,44 @@
include print "../io/print.dsa"
dw idt: 0xFFFF0000
setup_idt:
push bpr
mov spr, bpr
// load the IDT into the IDR
ldw idt, idr
mov bpr, spr
pop bpr
return
setup_hard_fault_handler:
push bpr
mov spr, bpr
lwi handle_hard_fault, rg0
stw rg0, idr, 4
mov bpr, spr
pop bpr
return
dw hard_fault_err: "FATAL: Illegal Instruction or Memory Access!"
handle_hard_fault:
call print::reset
lwi hard_fault_err, rg0
push rg0
call print::print
pop zero
hlt
trigger:
push bpr
mov spr, bpr
int 0x01
mov bpr, spr
pop bpr
return
-18
View File
@@ -1,18 +0,0 @@
fib_n:
pop ret
pop rg0 // n
lli 0, rg1
lli 1, rg2
start:
add rg1, rg2, acc
push rg1
mov rg2, rg1
mov acc, rg2
cmp rg0, zero
dec rg0
jgt start
jmp 4, ret
+331
View File
@@ -0,0 +1,331 @@
// lib:
// print.dsa
// usage:
//
// include print "<relative path>""
//
// usage for print:
// push (register containing address of string)
// push pcx
// jmp print::print
//
// usage for reset:
// push pcx
// jmp print::reset
//
// usage for clear:
// push pcx
// jmp print::clear
//
// usage for print_byte:
// push (register containing byte)
// push pcx
// jmp print::print_byte
//
// usage for print_word:
// push (register containing word)
// push pcx
// jmp print::print_word
//
// usage for print_num:
// push (register containing number to print in decimal)
// push pcx
// jmp print::print_num
//
include maths "../maths/core.dsa"
dw display: 0x20000
dw current: 0x20000
// ------------------------------------------
// prints the string at addr(arg[0]) to the screen. (no trailing whitespace unless explicitly provided)
print:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
_print_loop:
ldb rg0, acc
cmp acc, zero
jeq _end
stb acc, rg1
addi rg0, 1
addi rg1, 1
jmp _print_loop
// ------------------------------------------
println:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
_println_loop:
ldb rg0, acc
cmp acc, zero
jeq _println_end
stb acc, rg1
addi rg0, 1
addi rg1, 1
jmp _println_loop
_println_end:
call print_newline
jmp _end
// ------------------------------------------
// prints the value of arg[0] to the screen.
print_word:
// initialise
push bpr
mov spr, bpr
// load byte into acc
ldw bpr, rg0, 8
ldw current, rg1
addi rg1, 3
stb rg0, rg1
subi rg1, 1
shr rg0, 8
stb rg0, rg1
subi rg1, 1
shr rg0, 8
stb rg0, rg1
subi rg1, 1
shr rg0, 8
stb rg0, rg1
addi rg1, 4
jmp _end
// ------------------------------------------
// prints the last byte of arg[0] to the screen.
print_byte:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
stb rg0, rg1
addi rg1, 1
jmp _end
// ------------------------------------------
// prints the value of arg[0] to the screen in hex.
print_hex_word:
push bpr
mov spr, bpr
ldw current, rg1
ldb bpr, rg0, 8
push rg0
call _print_hex_byte
addi spr, 4
ldb bpr, rg0, 9
push rg0
call _print_hex_byte
addi spr, 4
ldb bpr, rg0, 10
push rg0
call _print_hex_byte
addi spr, 4
ldb bpr, rg0, 11
push rg0
call _print_hex_byte
addi spr, 4
jmp _end
// ------------------------------------------
// prints the last byte of arg[0] to the screen in hex.
print_hex_byte:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
call _print_hex_byte
jmp _end
// function body
_print_hex_byte:
// mask to get lower nibble
lli 0xF, rg2
// save rg0 state
push rg0
shr rg0, 4
and rg0, rg2, rg0
call _print_hex_nibble
pop rg0
and rg0, rg2, rg0
call _print_hex_nibble
return
// print a hex digit
_print_hex_nibble:
lli 10, rg3
cmp rg0, rg3
jlt _print_hex_nibble_number
addi rg0, 0x37, rg0
stb rg0, rg1
addi rg1, 1
return
// helper function.
_print_hex_nibble_number:
addi rg0, 0x30, rg0
stb rg0, rg1
addi rg1, 1
return
// ------------------------------------------
// print whitespace
print_whitespace:
push bpr
mov spr, bpr
ldw current, rg1
lli 0x20, rg0
stb rg0, rg1
addi rg1, 1
jmp _end
// ------------------------------------------
// print newline
print_newline:
push bpr
mov spr, bpr
// load variables into registers
ldw display, rg0
ldw current, rg1
// get the offset from the display base
sub rg1, rg0, rg0
lwi 80, rg2
pusha 3
push rg0
push rg2
call maths::divmod
pop zero // result
pop rg3 // remainder
popa 3
sub rg1, rg3, rg2
addi rg2, 80, rg1
// _end saves the display state
jmp _end
// ------------------------------------------
// prints arg[0] as a decimal number to the screen.
print_num:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // load number to print
lli 0, rg5 // rg5 = digit counter
// check if number is zero
cmp rg0, zero
jne _print_num_extract_digits
// special case: print '0' for zero
lli 0x30, rg6
push rg6 // push digit to stack buffer
lli 1, rg5 // we have 1 digit
jmp _print_num_output
_print_num_extract_digits:
// divide by 10 repeatedly to get digits
cmp rg0, zero
jeq _print_num_output
// call divmod(rg0, 10)
push rg0 // dividend
lli 10, rg1
push rg1 // divisor (10)
call maths::divmod
pop rg0 // quotient (continue dividing this)
pop rg1 // remainder (the digit)
// convert digit to ASCII and push to stack buffer
addi rg1, 0x30, rg6 // convert to ASCII
push rg6 // push digit to stack
inc rg5 // increment digit counter
jmp _print_num_extract_digits
_print_num_output:
// now print digits (pop them off in reverse order)
ldw current, rg1 // get display pointer
_print_num_output_loop:
// check if we've printed all digits
cmp rg5, zero
jeq _print_num_done
// pop digit and print it
pop rg6
stb rg6, rg1
addi rg1, 1
dec rg5
jmp _print_num_output_loop
_print_num_done:
jmp _end
// ------------------------------------------
// resets the cursor position on the screen to 0x20000. (0,0)
reset:
push bpr
mov spr, bpr
ldw display, rg1
jmp _end
// ------------------------------------------
// clears the screen
clear:
push bpr
mov spr, bpr
// display size = 2000 bytes / 500 words
lli 500 rg0
ldw display, rg1
_clear_loop:
dec rg0
stw zero, rg1
addi rg1, 4
cmp rg0, zero
jgt _clear_loop
jmp _end
// ------------------------------------------
// return
_end:
stw rg1, current
mov bpr, spr
pop bpr
return
+104
View File
@@ -0,0 +1,104 @@
// multiply.dsa
// usage:
//
// include multiply "<relative path>"
//
// usage for multiply:
// push (arg1)
// push (arg0)
// call multiply::multiply
// pop (arg0)
// pop (arg1)
multiply:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // load op 2
ldw bpr, rg1, 12 // load op 1
lwi 0, rg2 // initialise rg2 to zero
_multiply_loop:
add rg2, rg0, rg2
dec rg1
cmp rg1, zero
jgt _multiply_loop
_multiply_end:
stw rg2, bpr, 8
mov bpr, spr
pop bpr
return
divmod:
push bpr
mov spr, bpr
ldw bpr, rg1, 8 // load op 2
ldw bpr, rg0, 12 // load op 1
lli 0, rg3
_divmod_loop:
cmp rg0, rg1
jlt _divmod_end
sub rg0, rg1, rg0
inc rg3
jmp _divmod_loop
_divmod_end:
// store div in first arg
// store mod in second arg
stw rg3, bpr, 8
stw rg0, bpr, 12
mov bpr, spr
pop bpr
return
// multiply.dsa - improved version
// Multiplies two 32-bit numbers using shift-and-add
//
// Usage:
// push operand2 (multiplier)
// push operand1 (multiplicand)
// call multiply::multiply
// pop result
// pop zero (discard second argument)
new_multiply:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // rg0 = multiplicand
ldw bpr, rg1, 12 // rg1 = multiplier
lli 0, rg2 // rg2 = result (accumulator)
lli 32, rg3 // rg3 = bit counter
mult_loop:
// Check if lowest bit of multiplier is 1
lli 1, acc
and rg1, acc, acc // acc = rg1 & 1
cmp acc, zero
jeq skip_add // if (rg1 & 1) == 0, skip addition
// Add multiplicand to result
add rg2, rg0, rg2
skip_add:
shl rg0, 1 // shift multiplicand left
shr rg1, 1 // shift multiplier right
dec rg3
cmp rg3, zero
jgt mult_loop
stw rg2, bpr, 8 // store result
mov bpr, spr
pop bpr
return
+24
View File
@@ -0,0 +1,24 @@
include print "../io/print.dsa"
fib_n:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // load arg
lwi 0, rg1
lwi 1, rg2
_start:
add rg1, rg2, rg3
mov rg2, rg1
mov rg3, rg2
dec rg0
cmp rg0, zero
jgt _start
stw rg3, bpr, 8
mov bpr, spr
pop bpr
return
+100
View File
@@ -0,0 +1,100 @@
dw heap_start: 196608
dw heap_end: 262144
dw heap_current: 196608
new:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
lli 12, rg1
add rg0, rg1, rg2
ldw heap_current, rg1
add rg1, rg2, rg3
ldw heap_end, rg4
cmp rg3, rg4
lli 0, rg5
jle _cmp_end_2
lli 1, rg5
_cmp_end_2:
cmp rg5, zero
jeq _else_4
_then_3:
lli 0, rg4
stw rg4, bpr, 8
jmp _ret
jmp _end_5
_else_4:
nop
_end_5:
lli 12, rg4
add rg1, rg4, rg5
add rg1, rg2, rg4
stw rg5, rg1
lli 4, rg6
add rg1, rg6, rg7
stw rg5, rg7
lli 8, rg6
add rg1, rg6, rg7
stw rg4, rg7
stw rg3, heap_current
stw rg1, bpr, 8
jmp _ret
alloc:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw bpr, rg1, 12
lli 4, rg2
add rg0, rg2, rg3
ldw rg3, rg2
lli 8, rg3
add rg0, rg3, rg4
ldw rg4, rg3
add rg2, rg1, rg4
cmp rg4, rg3
lli 0, rg5
jle _cmp_end_6
lli 1, rg5
_cmp_end_6:
cmp rg5, zero
jeq _else_8
_then_7:
lli 0, rg5
stw rg5, bpr, 8
jmp _ret
jmp _end_9
_else_8:
nop
_end_9:
lli 4, rg5
add rg0, rg5, rg6
stw rg4, rg6
stw rg2, bpr, 8
jmp _ret
destroy:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
lli 0, rg1
stw rg1, bpr, 8
jmp _ret
reset_all:
push bpr
mov spr, bpr
ldw heap_start, rg0
stw rg0, heap_current
lli 0, rg0
stw rg0, bpr, 8
jmp _ret
_ret:
mov bpr, spr
pop bpr
return
+77
View File
@@ -0,0 +1,77 @@
// Arena Allocator
// Supports multiple arenas that can be destroyed independently
// Much more practical than a simple bump allocator
// Global heap management
static heap_start: u32 = 0x30000;
static heap_end: u32 = 0x40000;
static heap_current: u32 = 0x30000;
// Arena structure (stored at the start of each arena):
// [0-3]: start_address (u32)
// [4-7]: current_position (u32)
// [8-11]: end_address (u32)
// Total header size: 12 bytes
// Create a new arena with given size
// Returns pointer to arena handle (or 0 if failed)
fn arena_create(size: u32) -> u32 {
let total_size: u32 = size + 12;
let arena_ptr: u32 = heap_current;
let new_current: u32 = arena_ptr + total_size;
// Check if we have space
if new_current > heap_end {
return 0;
}
// Calculate arena data region
let data_start: u32 = arena_ptr + 12;
let data_end: u32 = arena_ptr + total_size;
// Initialize arena header
// Note: In real implementation, you'd use pointer writes here
// For now, using placeholder comments:
*arena_ptr = data_start; // start_address
*(arena_ptr + 4) = data_start; // current_position
*(arena_ptr + 8) = data_end; // end_address
heap_current = new_current;
return arena_ptr;
}
// Allocate from an arena
// Returns pointer to allocated memory (or 0 if failed)
fn arena_alloc(arena: u32, size: u32) -> u32 {
// Read current position from arena
let current: u32 = *(arena + 4);
let end: u32 = *(arena + 8);
let new_current: u32 = current + size;
// Check if arena has space
if new_current > end {
return 0;
}
// Update current position in arena
*(arena + 4) = new_current;
return current;
}
// Destroy an arena (in bump allocator, this is a no-op)
// In a real allocator, you'd mark the memory as free
fn arena_destroy(arena: u32) {
// In a true allocator, mark memory as reusable
// For bump allocator, we can't reclaim memory
// unless we destroy ALL arenas and reset
return 0;
}
// Reset entire heap (destroys ALL arenas)
fn reset_all() {
heap_current = heap_start;
return 0;
}
+216
View File
@@ -0,0 +1,216 @@
// block_alloc.dsa
// Fixed-size block allocator
//
// Memory layout:
// [base + 0]: free list head pointer (pointer to first free block, or 0 if none)
// [base + 4]: block size
// [base + 8]: total blocks
// [base + 12]: base address of block pool
// [base + 16+]: block pool (each block starts with a 4-byte next pointer)
//
// Usage:
// include block_alloc "./lib/memory/block_alloc.dsa"
//
// For init:
// push num_blocks (e.g., 32)
// push block_size (e.g., 64 bytes)
// call block_alloc::init
// pop block_size
// pop num_blocks
// ; result in spr+8 (allocator handle)
//
// For alloc:
// push allocator_handle
// call block_alloc::alloc
// pop allocator_handle
// ; result in spr+8 (pointer to block, or 0 if out of memory)
//
// For free:
// push block_pointer
// push allocator_handle
// call block_alloc::free
// pop allocator_handle
// pop block_pointer
dw heap_start: 0x30000 // Start of our heap area
// Initialize the allocator
// Args: block_size, num_blocks
// Returns: allocator handle (pointer to metadata)
init:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // block_size
ldw bpr, rg1, 12 // num_blocks
// Allocate metadata (16 bytes) + pool space
ldw heap_start, rg2 // base address for this allocator
mov rg2, rg3 // save base in rg3
// Calculate total size needed: 16 + (block_size * num_blocks)
// We'll use a simple multiplication by repeated addition
mov rg0, rg4 // block_size to rg4
mov rg1, rg5 // num_blocks to rg5
lli 0, acc // accumulator for multiplication
_multiply_loop:
cmp rg5, zero
jeq _multiply_done
add acc, rg4, acc
dec rg5
jmp _multiply_loop
_multiply_done:
// acc now contains block_size * num_blocks
addi acc, 16 // add metadata size
// Update heap_start for next allocation
add rg2, acc, acc
stw acc, heap_start
// Now set up metadata at rg3 (base)
// [base + 0]: free list head (will point to first block)
// [base + 4]: block_size
// [base + 8]: total blocks
// [base + 12]: pool base address
addi rg3, 16, rg6 // rg6 = pool base
stw rg6, rg3 // store pool base as free list head initially
stw rg0, rg3, 4 // store block_size
stw rg1, rg3, 8 // store total blocks
stw rg6, rg3, 12 // store pool base address
// Now initialize the free list
// Each block's first 4 bytes point to the next block
// rg6 = current block pointer
// rg0 = block_size
// rg1 = num_blocks (counter)
dec rg1 // we'll count down from num_blocks-1
_init_loop:
cmp rg1, zero
jeq _init_loop_done
// Calculate next block address: current + block_size
add rg6, rg0, rg7 // rg7 = next block address
// Store next pointer at current block
stw rg7, rg6
// Move to next block
mov rg7, rg6
dec rg1
jmp _init_loop
_init_loop_done:
// Last block points to null (0)
lli 0, acc
stw acc, rg6
// Return allocator handle (base address - 16 to get back to metadata start)
stw rg3, bpr, 8
mov bpr, spr
pop bpr
return
// Allocate a block
// Args: allocator_handle
// Returns: pointer to block (or 0 if out of memory)
alloc:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // allocator handle (metadata base)
// Load free list head
ldw rg0, rg1 // rg1 = free list head
// Check if free list is empty
cmp rg1, zero
jeq _alloc_fail
// Free list is not empty, pop the first block
// Load the next pointer from the block we're allocating
ldw rg1, rg2 // rg2 = next free block
// Update free list head to point to next block
stw rg2, rg0
// Return the allocated block (rg1)
stw rg1, bpr, 8
jmp _alloc_done
_alloc_fail:
// No free blocks, return 0
lli 0, acc
stw acc, bpr, 8
_alloc_done:
mov bpr, spr
pop bpr
return
// Free a block
// Args: allocator_handle, block_pointer
// Returns: nothing (but could return error code if block is invalid)
free:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // allocator handle
ldw bpr, rg6, 12 // pointer to the block pointer to free
ldw rg6, rg1 // rg1 = block pointer to free
// Load current free list head
ldw rg0, rg2 // rg2 = current head
// Set the freed block's next pointer to current head
stw rg2, rg1
// Update free list head to point to freed block
stw rg1, rg0
// Update the freed block's previous pointer to NULL
lli 0, rg1
stw rg1, rg6
mov bpr, spr
pop bpr
return
// Debug function: get stats
// Args: allocator_handle
// Returns: nothing (but could populate a stats structure)
get_stats:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // allocator handle
// Count free blocks by traversing the free list
ldw rg0, rg1 // rg1 = free list head
lli 0, rg2 // rg2 = counter
count_loop:
cmp rg1, zero
jeq count_done
inc rg2
ldw rg1, rg1 // move to next block
jmp count_loop
count_done:
// rg2 now contains number of free blocks
// Could store this somewhere or return it
stw rg2, bpr, 8
mov bpr, spr
pop bpr
return
-30
View File
@@ -1,30 +0,0 @@
// multiply.dsa
// usage:
//
// include multiply "<relative path>"
//
// usage for multiply:
// push (arg1)
// push (arg0)
// call multiply::multiply
// pop (arg0)
// pop (arg1)
multiply:
push bpr
mov spr, bpr
ldw bpr, rg0, 8 // load op 1
ldw bpr, rg1, 12 // load op 2
start:
add acc, rg0, acc
dec rg1
cmp rg1, zero
jgt start
end:
mov bpr, spr
pop bpr
return
-115
View File
@@ -1,115 +0,0 @@
// lib:
// print.dsa
// usage:
//
// include print "<relative path>""
//
// usage for print:
// push (register containing address of string)
// push pcx
// jmp print::print
//
// usage for reset:
// push pcx
// jmp print::reset
//
// usage for clear:
// push pcx
// jmp print::clear
//
// usage for print_byte:
// push (register containing byte)
// push pcx
// jmp print::print_byte
//
// usage for print_word:
// push (register containing word)
// push pcx
// jmp print::print_word
//
dw display: 0x20000
dw current: 0x20000
// ------------------------------------------
// prints the string at addr(arg[0]) to the screen.
print:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
_print_loop:
ldb rg0, acc
stb acc, rg1
addi rg0, 1
addi rg1, 1
cmp acc, zero
jne _print_loop
jmp _end
// ------------------------------------------
// prints the value of arg[0] to the screen.
print_word:
// initialise
push bpr
mov spr, bpr
// load byte into acc
ldw bpr, rg0, 8
ldw current, rg1
stw rg0, rg1
addi rg1, 4
jmp _end
// ------------------------------------------
// prints the last byte of arg[0] to the screen.
print_byte:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw current, rg1
stb rg0, rg1
addi rg1, 1
jmp _end
// ------------------------------------------
// resets the cursor position on the screen to 0x20000. (0,0)
reset:
push bpr
mov spr, bpr
ldw display, rg1
jmp _end
// ------------------------------------------
// clears the screen
clear:
push bpr
mov spr, bpr
// display size = 2000 bytes / 500 words
lli 500 rg0
ldw display, rg1
_clear_loop:
dec rg0
stw zero, rg1
addi rg1, 4
cmp rg0, zero
jgt _clear_loop
jmp _end
// ------------------------------------------
// return
_end:
stw rg1, current
mov bpr, spr
pop bpr
return
+12
View File
@@ -0,0 +1,12 @@
// program to just test compute power
dw large_num: 0x333333 // 333,333 instructions
start:
ldw large_num, rg0
// run approx 1m instructions
loop:
dec rg0
cmp rg0, zero
jgt loop
hlt

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