10 Commits

37 changed files with 1290 additions and 555 deletions
+7
View File
@@ -0,0 +1,7 @@
[build]
rustc-wrapper = "sccache"
# Enable to cut unused deps.
# rustflags = ["-D", "unused-crate-dependencies"]
[future-incompat-report]
frequency = "always"
Generated
+171 -27
View File
@@ -165,6 +165,56 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "anstream"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "arboard" name = "arboard"
version = "3.5.0" version = "3.5.0"
@@ -216,6 +266,7 @@ dependencies = [
name = "assembler" name = "assembler"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"clap",
"common", "common",
"num_cpus", "num_cpus",
"threadpool", "threadpool",
@@ -424,9 +475,9 @@ dependencies = [
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.4.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "bit-set" name = "bit-set"
@@ -603,6 +654,46 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "clap"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]] [[package]]
name = "clipboard-win" name = "clipboard-win"
version = "5.4.0" version = "5.4.0"
@@ -622,6 +713,12 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]] [[package]]
name = "colorful" name = "colorful"
version = "0.3.2" version = "0.3.2"
@@ -641,6 +738,9 @@ dependencies = [
[[package]] [[package]]
name = "common" name = "common"
version = "0.2.0" version = "0.2.0"
dependencies = [
"object",
]
[[package]] [[package]]
name = "concurrent-queue" name = "concurrent-queue"
@@ -1091,12 +1191,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.12" version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@@ -1601,6 +1701,12 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.15" version = "1.0.15"
@@ -1641,9 +1747,9 @@ dependencies = [
[[package]] [[package]]
name = "jpeg-decoder" name = "jpeg-decoder"
version = "0.3.1" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
@@ -1674,9 +1780,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.173" version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]] [[package]]
name = "libloading" name = "libloading"
@@ -1909,18 +2015,19 @@ dependencies = [
[[package]] [[package]]
name = "num_enum" name = "num_enum"
version = "0.7.3" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a"
dependencies = [ dependencies = [
"num_enum_derive", "num_enum_derive",
"rustversion",
] ]
[[package]] [[package]]
name = "num_enum_derive" name = "num_enum_derive"
version = "0.7.3" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d"
dependencies = [ dependencies = [
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
@@ -2208,12 +2315,31 @@ dependencies = [
"objc2-foundation 0.2.2", "objc2-foundation 0.2.2",
] ]
[[package]]
name = "object"
version = "0.37.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03fd943161069e1768b4b3d050890ba48730e590f57e56d4aa04e7e090e61b4a"
dependencies = [
"crc32fast",
"hashbrown",
"indexmap",
"memchr",
"rustc-std-workspace-alloc",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.21.3" version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]] [[package]]
name = "option-ext" name = "option-ext"
version = "0.2.0" version = "0.2.0"
@@ -2435,9 +2561,9 @@ dependencies = [
[[package]] [[package]]
name = "profiling" name = "profiling"
version = "1.0.16" version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
@@ -2495,9 +2621,9 @@ dependencies = [
[[package]] [[package]]
name = "r-efi" name = "r-efi"
version = "5.2.0" version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]] [[package]]
name = "rand" name = "rand"
@@ -2582,6 +2708,12 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustc-std-workspace-alloc"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d441c3b2ebf55cebf796bfdc265d67fa09db17b7bb6bd4be75c509e1e8fec3"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.44" version = "0.38.44"
@@ -2831,6 +2963,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "strum" name = "strum"
version = "0.26.3" version = "0.26.3"
@@ -2855,9 +2993,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.103" version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3046,9 +3184,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-attributes" name = "tracing-attributes"
version = "0.1.29" version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3137,6 +3275,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.17.0" version = "1.17.0"
@@ -3987,9 +4131,9 @@ checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d"
[[package]] [[package]]
name = "xcursor" name = "xcursor"
version = "0.3.8" version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" checksum = "635887f4315a33cb714eb059bdbd7c1c92bfa71bc5b9d5115460502f788c2ab5"
[[package]] [[package]]
name = "xdg-home" name = "xdg-home"
@@ -4151,18 +4295,18 @@ dependencies = [
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.25" version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.25" version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
+9
View File
@@ -1,3 +1,5 @@
cargo-features = ["codegen-backend"]
[workspace] [workspace]
members = ["emulator", "common", "assembler", "dsa_editor"] members = ["emulator", "common", "assembler", "dsa_editor"]
resolver = "3" resolver = "3"
@@ -6,3 +8,10 @@ resolver = "3"
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.
+1
View File
@@ -13,6 +13,7 @@ name = "assembler"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
clap = { version = "4.5.40", features = ["derive"] }
common = { path = "../common" } common = { path = "../common" }
num_cpus = "1.17.0" num_cpus = "1.17.0"
threadpool = "1.8.1" threadpool = "1.8.1"
+28 -33
View File
@@ -1,34 +1,29 @@
++++++++++++++++++++++++++++++++++++++++++++ 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
View File
@@ -0,0 +1,20 @@
use clap::{Parser, ValueEnum};
#[derive(Debug, Parser, Default)]
pub struct Args {
/// The output format to assemble to. Currently just ELF or a flat binary.
#[arg(value_enum)]
output_format: Option<OutputFormat>,
/// Whether the relocatable object files should be statically linked into a single executable or library.
link: bool,
}
#[derive(Debug, Clone, Copy, ValueEnum, Default)]
/// The executable format the output should take.
pub enum OutputFormat {
/// An ELF file.
#[default]
Elf,
/// A flat binary file.
Flat,
}
+4 -2
View File
@@ -12,7 +12,7 @@ pub fn lexer(mut program: String, module: u64) -> Result<Vec<Token>, AssembleErr
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 +23,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);
} }
+4 -6
View File
@@ -113,11 +113,10 @@ impl Parser {
let dest = expect_type!(self.next()?, Register)?; let dest = expect_type!(self.next()?, Register)?;
let mut offset = Token::Immediate(0); let mut offset = Token::Immediate(0);
if let Ok(next) = self.peek_next() { if let Ok(next) = self.peek_next()
if expect_type!(next, Immediate).is_ok() { && expect_type!(next, Immediate).is_ok() {
offset = self.next()?; offset = self.next()?;
} }
}
args = vec![base, dest, offset]; args = vec![base, dest, offset];
} }
@@ -125,11 +124,10 @@ impl Parser {
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); let mut offset = Token::Immediate(0);
if let Ok(next) = self.peek_next() { if let Ok(next) = self.peek_next()
if expect_type!(next, Immediate).is_ok() { && expect_type!(next, Immediate).is_ok() {
offset = self.next()?; offset = self.next()?;
} }
}
args = vec![base, dest, offset]; args = vec![base, dest, offset];
} }
View File
+7 -1
View File
@@ -12,12 +12,18 @@
clippy::match_wildcard_for_single_variants clippy::match_wildcard_for_single_variants
)] )]
pub mod args;
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 num_cpus as _;
use threadpool as _;
+15 -4
View File
@@ -1,10 +1,21 @@
use assembler::{brainf, prelude::*}; use common as _;
use num_cpus as _;
use threadpool as _;
use assembler::{
prelude::*,
tooling::{brainf, project},
};
use clap::Parser;
use std::{fs, io::Write, path::PathBuf}; use std::{fs, io::Write, path::PathBuf};
fn main() { fn main() {
// Parse command line arguments // Parse command line arguments
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
let _clap_args = assembler::args::Args::parse();
if args.len() == 2 && args[1] == "init" { if args.len() == 2 && args[1] == "init" {
project::tool_libcreate(); project::tool_libcreate();
std::process::exit(0); std::process::exit(0);
@@ -16,7 +27,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 +35,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);
} }
} }
@@ -50,7 +61,7 @@ fn main() {
for instruction in result { for instruction in result {
if let Err(e) = fs::write(output_path, instruction.encode().to_be_bytes()) { if let Err(e) = fs::write(output_path, 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);
} }
} }
+1
View File
@@ -1 +1,2 @@
pub mod brainf;
pub mod project; pub mod project;
+1
View File
@@ -5,3 +5,4 @@ edition.workspace = true
authors.workspace = true authors.workspace = true
[dependencies] [dependencies]
object = { version = "0.37.1", default-features = false, features = ["elf", "std", "read", "read_core", "write_std", "write", "alloc", "build"] }
+3
View File
@@ -0,0 +1,3 @@
# Common types and methods for the DSA
This library contains the instruction set, encoding and decoding routines, and ELF encoding and loading routines (WIP).
+8
View File
@@ -0,0 +1,8 @@
//! ELF file creation and parsing routines.
use object::{Endianness, build::elf::Builder};
#[allow(clippy::missing_const_for_fn)]
pub fn write() {
let _builder = Builder::new(Endianness::Little, false);
}
+11 -5
View File
@@ -3,6 +3,8 @@ 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;
@@ -10,6 +12,8 @@ pub type Address = u32;
impl Interrupt { impl Interrupt {
const fn as_u8(self) -> u8 { 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 +23,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),
}
} }
} }
@@ -73,7 +78,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,
+1
View File
@@ -12,6 +12,7 @@
clippy::match_wildcard_for_single_variants clippy::match_wildcard_for_single_variants
)] )]
pub mod elf;
pub mod instructions; pub mod instructions;
pub mod prelude { pub mod prelude {
+4 -2
View File
@@ -17,7 +17,6 @@ required-features = ["config"]
common = { path = "../common" } common = { path = "../common" }
assembler = { path = "../assembler" } assembler = { path = "../assembler" }
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 = "1.6.0", optional = true }
@@ -30,7 +29,7 @@ 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 +37,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"
+4 -4
View File
@@ -180,10 +180,10 @@ impl Drop for RpcClient {
fn drop(&mut self) { fn drop(&mut self) {
self.stop(); self.stop();
if let Some(handle) = self.thread_handle.take() { if let Some(handle) = self.thread_handle.take()
if let Some(handle) = Arc::into_inner(handle) { && let Some(handle) = Arc::into_inner(handle)
let _ = handle.join(); {
} let _ = handle.join();
} }
} }
} }
+120 -94
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,33 @@ 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 addr;
let mut history = Vec::<(u32, Instruction)>::new(); let mut history = Vec::<(u32, Instruction)>::new();
let size = 256; let size = 256;
let memory_view = processor.memory.read_range(addr, size); state_tx
let initial_state = state(&mut processor, running, 0, memory_view, &mut history); .send(StateUpdate::Running(Running::Paused))
let _ = state_tx.send(initial_state); .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 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 {
@@ -91,52 +93,120 @@ pub fn run_emulator(
processor.reset(); processor.reset();
} }
Command::Step => { Command::Step(x) => {
running = Running::Paused; step = x;
// 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) => {
processor.memory.write_range(offset, data); update = true;
processor
.memory
.write_range(offset, data)
.unwrap_or_else(|_| {
report_err(
state_tx,
"Failed to write memory range!",
&mut processor,
);
});
} }
#[expect(unused_assignments)]
Command::Interrupt(_interrupt) => { Command::Interrupt(_interrupt) => {
update = true;
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).unwrap_or_else(|_| {
report_err(
state_tx,
"Failed to read memory range!",
&mut processor,
);
Vec::new()
}),
));
}
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())
.unwrap_or_else(|_| {
report_err(
state_tx,
"Failed to write memory block!",
&mut processor,
);
});
}
_ => {}
} }
}
let memory_view = processor.memory.read_range(addr, size); if step > 0 {
let state = state( step -= 1;
&mut processor, update = true;
running, running = Running::Paused;
instruction_count,
memory_view,
&mut history,
);
println!("state"); // Execute one cycle.
match processor.cycle() {
let _ = state_tx.send(state); Ok((addr, instruction)) => {
history.push((addr, instruction));
}
Err(why) => {
let pcx = processor.get(Register::Pcx);
report_err(
state_tx,
&format!(
"Could not decode instruction at {pcx:x}. Reason: {why}"
),
&mut processor,
);
}
}
instruction_count += 1;
continue;
} }
if running == Running::Running { if running == Running::Running {
let mut update = false; update = true;
// Execute one cycle. // Execute one cycle.
let instruction = match processor.cycle() { let instruction = match processor.cycle() {
@@ -149,60 +219,16 @@ pub fn run_emulator(
}; };
history.push(instruction); history.push(instruction);
// let instruction = match Instruction::decode(cpu_lock.get(Register::Cir))
// {};
if matches!(instruction.1, Instruction::Halt) { if matches!(instruction.1, Instruction::Halt) {
running = Running::Halted; running = Running::Halted;
update = true;
} }
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.begin_interrupt(Interrupt::HardFault);
running: Running, let _ = state_tx.send(StateUpdate::Error(why.to_string()));
instruction_count: usize,
memory_view: Vec<u8>,
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 },
}
} }
+71 -23
View File
@@ -1,13 +1,43 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::emulator::system::model::ProcessorError;
pub trait MemoryUnit: Send + Sync { pub trait MemoryUnit: Send + Sync {
fn reset(&mut self); fn reset(&mut self);
fn read_byte(&mut self, addr: u32) -> u8; fn read_byte(&mut self, addr: u32) -> Result<u8, ProcessorError>;
fn write_byte(&mut self, addr: u32, value: u8); fn write_byte(&mut self, addr: u32, value: u8) -> Result<(), ProcessorError>;
fn read_word(&mut self, addr: u32) -> 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) -> Result<Vec<u8>, ProcessorError> {
let mut data = Vec::with_capacity(size as usize);
for i in 0..size {
data.push(self.read_byte(addr + i)?);
}
Ok(data)
}
fn write_range(&mut self, addr: u32, value: Vec<u8>) -> Result<(), ProcessorError> {
for (i, byte) in value.into_iter().enumerate() {
self.write_byte(addr + i as u32, byte)?;
}
Ok(())
}
fn read_block(&mut self, addr: u32) -> Result<[u8; 256], ProcessorError> {
let mut data = [0; 256];
for (i, byte) in data.iter_mut().enumerate() {
*byte = self.read_byte(addr + i as u32)?;
}
Ok(data)
}
fn write_block(&mut self, addr: u32, data: [u8; 256]) -> Result<(), ProcessorError> {
for (i, byte) in data.iter().enumerate() {
self.write_byte(addr + i as u32, *byte)?;
}
Ok(())
}
} }
pub struct MainStore { pub struct MainStore {
@@ -64,59 +94,77 @@ impl MemoryUnit for MainStore {
self.data.clear(); self.data.clear();
} }
fn read_byte(&mut self, addr: u32) -> u8 { fn read_byte(&mut self, addr: u32) -> Result<u8, ProcessorError> {
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] Ok(block.data[offset as usize])
} }
fn read_word(&mut self, addr: u32) -> u32 { fn read_word(&mut self, addr: u32) -> Result<u32, ProcessorError> {
if addr % 4 != 0 {
return Err(ProcessorError::BadMemoryAccess(addr));
}
let (block_addr, offset) = Self::segment_addr(addr); let (block_addr, offset) = Self::segment_addr(addr);
println!("reading word from {block_addr:x?} + {offset}");
let block = self.mut_block(block_addr); let block = self.mut_block(block_addr);
let mut bytes = [0; 4]; let mut bytes = [0; 4];
bytes[0] = block.data[offset as usize]; bytes[0] = block.data[offset as usize];
bytes[1] = block.data[(offset + 1) as usize]; bytes[1] = block.data[(offset + 1) as usize];
bytes[2] = block.data[(offset + 2) as usize]; bytes[2] = block.data[(offset + 2) as usize];
bytes[3] = block.data[(offset + 3) as usize]; bytes[3] = block.data[(offset + 3) as usize];
u32::from_be_bytes(bytes) Ok(u32::from_be_bytes(bytes))
} }
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8> { fn read_range(&mut self, addr: u32, size: u32) -> Result<Vec<u8>, ProcessorError> {
let mut data = Vec::with_capacity(size as usize); let mut data = Vec::with_capacity(size as usize);
for i in 0..size { for i in 0..size {
data.push(self.read_byte(addr + i)); data.push(self.read_byte(addr + i)?);
} }
// println!("reading {data:?} from {addr:x?}"); Ok(data)
data
} }
fn write_byte(&mut self, addr: u32, value: u8) { fn write_byte(&mut self, addr: u32, value: u8) -> Result<(), ProcessorError> {
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.data[offset as usize] = value;
Ok(())
} }
fn write_word(&mut self, addr: u32, value: u32) { fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError> {
if addr % 4 != 0 {
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.data[offset as usize] = (value >> 24) as u8;
block.data[(offset + 1) as usize] = (value >> 16) as u8; block.data[(offset + 1) as usize] = (value >> 16) as u8;
block.data[(offset + 2) as usize] = (value >> 8) as u8; block.data[(offset + 2) as usize] = (value >> 8) as u8;
block.data[(offset + 3) as usize] = value as u8; block.data[(offset + 3) as usize] = value as u8;
Ok(())
} }
fn write_range(&mut self, addr: u32, value: Vec<u8>) { fn write_range(&mut self, addr: u32, value: Vec<u8>) -> Result<(), ProcessorError> {
// println!("writing {value:?} to {addr:x?}");
for (i, byte) in value.into_iter().enumerate() { for (i, byte) in value.into_iter().enumerate() {
let (block_addr, offset) = Self::segment_addr(addr + i as u32); let (block_addr, offset) = Self::segment_addr(addr + i as u32);
let block = self.mut_block(block_addr); let block = self.mut_block(block_addr);
block.data[offset as usize] = byte; block.data[offset as usize] = byte;
} }
Ok(())
}
fn read_block(&mut self, addr: u32) -> Result<[u8; 256], ProcessorError> {
let (block_addr, _) = Self::segment_addr(addr);
let block = self.block(block_addr);
Ok(block.data)
}
fn write_block(&mut self, addr: u32, data: [u8; 256]) -> Result<(), ProcessorError> {
let (block_addr, _) = Self::segment_addr(addr);
let block = self.mut_block(block_addr);
block.data = data;
Ok(())
} }
} }
+134 -48
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, Instruction)>,
}
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, Instruction)>),
} }
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
@@ -195,47 +325,3 @@ impl RegFile {
} }
} }
} }
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);
}
}
}
+34 -42
View File
@@ -5,12 +5,10 @@ use std::{
use crate::emulator::system::{ use crate::emulator::system::{
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>,
@@ -21,11 +19,11 @@ pub struct Processor {
pub dustbin: u32, pub dustbin: u32,
} }
#[expect(dead_code)]
fn log(message: &str) { fn log(message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}"); 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 {
@@ -48,7 +46,7 @@ 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.
@@ -58,18 +56,16 @@ impl Processor {
// 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 val = self.memory.read_word(addr)?;
// Set CIR to the value of RAM[MAR]. // Set CIR to the value of RAM[MAR].
*self.reg(Register::Mar) = val; *self.reg(Register::Mar) = val;
// Decode and execute the instruction. // Decode and execute the instruction.
let instruction = Instruction::decode(val)?; let instruction = Instruction::decode(val)
.map_err(|_| ProcessorError::InvalidInstruction(val))?;
log(&instruction.to_string());
instruction.execute(self);
instruction.execute(self)?;
Ok((addr, instruction)) Ok((addr, instruction))
} }
@@ -89,7 +85,7 @@ impl Processor {
} }
} }
pub fn display(&mut self) -> Vec<u8> { pub fn display(&mut self) -> Result<Vec<u8>, ProcessorError> {
self.memory.read_range(0x20000, 2000) self.memory.read_range(0x20000, 2000)
} }
@@ -99,21 +95,7 @@ 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) |= flag as u32;
@@ -135,16 +117,18 @@ impl Processor {
*self.reg(Register::Pcx) = self.get(reg) + u32::from(offset); *self.reg(Register::Pcx) = self.get(reg) + u32::from(offset);
} }
fn begin_interrupt(&mut self, _int: Interrupt) { pub fn begin_interrupt(&mut self, _int: Interrupt) {
// first we get the address of the interrupt descriptor table. // first we get the address of the interrupt descriptor table.
todo!(); todo!();
} }
// TODO: remove this once implemented
#[allow(clippy::needless_pass_by_ref_mut)]
fn end_interrupt(&mut self) { fn end_interrupt(&mut self) {
todo!(); todo!();
} }
pub fn get_stack(&mut self, n: u32) -> Vec<u8> { pub fn get_stack(&mut self, n: u32) -> Result<Vec<u8>, ProcessorError> {
let addr = self.get(Register::Spr); let addr = self.get(Register::Spr);
let size = n * 4; let size = n * 4;
// returns the stack // returns the stack
@@ -170,12 +154,12 @@ 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.
@@ -193,7 +177,8 @@ impl Executable for Instruction {
// 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))?,
); );
} }
@@ -201,7 +186,8 @@ impl Executable for Instruction {
// 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,23 +196,28 @@ 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
@@ -235,7 +226,7 @@ impl Executable for Instruction {
cpu.memory.write_byte( cpu.memory.write_byte(
cpu.get(a.r2) + u32::from(a.immediate), cpu.get(a.r2) + u32::from(a.immediate),
cpu.get(a.r1) as u8, cpu.get(a.r1) as u8,
); )?;
} }
// Stores a half-word from SrcReg in memory address (base + offset) The // Stores a half-word from SrcReg in memory address (base + offset) The
@@ -244,16 +235,16 @@ impl Executable for Instruction {
// 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(cpu.get(a.r2) + u32::from(a.immediate), cpu.get(a.r1)); .write_word(cpu.get(a.r2) + u32::from(a.immediate), cpu.get(a.r1))?;
} }
// Loads a 16-bit literal value into reg, setting the bottom 16 bits of the // Loads a 16-bit literal value into reg, setting the bottom 16 bits of the
@@ -411,6 +402,7 @@ impl Executable for Instruction {
todo!() todo!()
} }
} }
Ok(())
} }
} }
+110 -38
View File
@@ -13,7 +13,9 @@ 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.get(Register::Rg0),
@@ -37,7 +39,9 @@ fn test_mov_instruction() {
None, None,
)); ));
mov_instr.execute(&mut cpu); mov_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg2), 0x1234_5678); assert_eq!(cpu.get(Register::Rg2), 0x1234_5678);
} }
@@ -53,7 +57,9 @@ fn test_mov_signed_instruction() {
None, None,
)); ));
mov_signed_instr.execute(&mut cpu); mov_signed_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg2), 0xFFFF_FFFF); assert_eq!(cpu.get(Register::Rg2), 0xFFFF_FFFF);
} }
@@ -61,7 +67,9 @@ fn test_mov_signed_instruction() {
fn test_load_byte_instruction() { fn test_load_byte_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
let addr = 0x100; let addr = 0x100;
cpu.memory.write_byte(addr, 0xAB); cpu.memory
.write_byte(addr, 0xAB)
.expect("Failed to write byte to memory");
*cpu.reg(Register::Rg1) = addr - 4; *cpu.reg(Register::Rg1) = addr - 4;
let load_byte_instr = Instruction::LoadByte(ITypeArgs::new( let load_byte_instr = Instruction::LoadByte(ITypeArgs::new(
@@ -70,7 +78,9 @@ fn test_load_byte_instruction() {
Some(Register::Rg2), Some(Register::Rg2),
)); ));
load_byte_instr.execute(&mut cpu); load_byte_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg2), 0x0000_00AB); assert_eq!(cpu.get(Register::Rg2), 0x0000_00AB);
} }
@@ -78,7 +88,9 @@ fn test_load_byte_instruction() {
fn test_load_byte_signed_instruction() { fn test_load_byte_signed_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
let addr = 0x100; let addr = 0x100;
cpu.memory.write_byte(addr, 0xFF); cpu.memory
.write_byte(addr, 0xFF)
.expect("Failed to write byte to memory");
*cpu.reg(Register::Rg1) = addr; *cpu.reg(Register::Rg1) = addr;
let load_byte_signed_instr = Instruction::LoadByteSigned(ITypeArgs::new( let load_byte_signed_instr = Instruction::LoadByteSigned(ITypeArgs::new(
@@ -87,7 +99,9 @@ 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(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg2), 0xFFFF_FFFF); assert_eq!(cpu.get(Register::Rg2), 0xFFFF_FFFF);
} }
@@ -95,7 +109,9 @@ fn test_load_byte_signed_instruction() {
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
.write_word(addr, 0x1234_5678)
.expect("Failed to write word to memory");
*cpu.reg(Register::Rg1) = addr; *cpu.reg(Register::Rg1) = addr;
let load_halfword_instr = Instruction::LoadHalfword(ITypeArgs::new( let load_halfword_instr = Instruction::LoadHalfword(ITypeArgs::new(
@@ -104,7 +120,9 @@ fn test_load_halfword_instruction() {
Some(Register::Rg2), Some(Register::Rg2),
)); ));
load_halfword_instr.execute(&mut cpu); load_halfword_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg2), 0x0000_1234); assert_eq!(cpu.get(Register::Rg2), 0x0000_1234);
} }
@@ -112,7 +130,9 @@ fn test_load_halfword_instruction() {
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
.write_word(addr, 0x1234_5678)
.expect("Failed to write word to memory");
*cpu.reg(Register::Rg1) = addr; *cpu.reg(Register::Rg1) = addr;
let load_word_instr = Instruction::LoadWord(ITypeArgs::new( let load_word_instr = Instruction::LoadWord(ITypeArgs::new(
@@ -121,7 +141,9 @@ fn test_load_word_instruction() {
Some(Register::Rg2), Some(Register::Rg2),
)); ));
load_word_instr.execute(&mut cpu); load_word_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg2), 0x1234_5678); assert_eq!(cpu.get(Register::Rg2), 0x1234_5678);
} }
@@ -138,8 +160,10 @@ fn test_store_byte_instruction() {
Some(Register::Rg1), Some(Register::Rg1),
)); ));
store_byte_instr.execute(&mut cpu); store_byte_instr.execute(&mut cpu).expect(
assert_eq!(cpu.memory.read_byte(addr), 0xAB); "Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.memory.read_byte(addr).expect("Emulator was slain by losing the game while attempting to execute instruction"), 0xAB);
} }
#[test] #[test]
@@ -155,8 +179,10 @@ 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]
@@ -172,7 +198,9 @@ fn test_add_instruction() {
None, None,
)); ));
add_instr.execute(&mut cpu); add_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg3), 40); assert_eq!(cpu.get(Register::Rg3), 40);
} }
@@ -189,7 +217,9 @@ fn test_sub_instruction() {
None, None,
)); ));
sub_instr.execute(&mut cpu); sub_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg3), 30); assert_eq!(cpu.get(Register::Rg3), 30);
} }
@@ -206,7 +236,9 @@ fn test_and_instruction() {
None, None,
)); ));
and_instr.execute(&mut cpu); and_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg3), 0b1000); assert_eq!(cpu.get(Register::Rg3), 0b1000);
} }
@@ -223,7 +255,9 @@ fn test_or_instruction() {
None, None,
)); ));
or_instr.execute(&mut cpu); or_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg3), 0b1110); assert_eq!(cpu.get(Register::Rg3), 0b1110);
} }
@@ -240,7 +274,9 @@ fn test_xor_instruction() {
None, None,
)); ));
xor_instr.execute(&mut cpu); xor_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg3), 0b0110); assert_eq!(cpu.get(Register::Rg3), 0b0110);
} }
@@ -256,7 +292,9 @@ fn test_not_instruction() {
None, None,
)); ));
not_instr.execute(&mut cpu); not_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg2), 0xF0F0_F0F0); assert_eq!(cpu.get(Register::Rg2), 0xF0F0_F0F0);
} }
@@ -273,7 +311,9 @@ 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));
assert!(!cpu.get_flag(Flag::GreaterThan)); assert!(!cpu.get_flag(Flag::GreaterThan));
@@ -293,7 +333,9 @@ 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));
assert!(cpu.get_flag(Flag::GreaterThan)); assert!(cpu.get_flag(Flag::GreaterThan));
@@ -313,7 +355,9 @@ 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));
assert!(!cpu.get_flag(Flag::GreaterThan)); assert!(!cpu.get_flag(Flag::GreaterThan));
@@ -328,7 +372,9 @@ fn test_increment_instruction() {
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(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg1), 43); assert_eq!(cpu.get(Register::Rg1), 43);
} }
@@ -340,7 +386,9 @@ fn test_decrement_instruction() {
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(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg1), 41); assert_eq!(cpu.get(Register::Rg1), 41);
} }
@@ -356,7 +404,9 @@ fn test_shift_left_with_shamt() {
Some(2), Some(2),
)); ));
shl_instr.execute(&mut cpu); shl_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg1), 0b10_1000); assert_eq!(cpu.get(Register::Rg1), 0b10_1000);
} }
@@ -372,7 +422,9 @@ fn test_shift_right_with_shamt() {
Some(2), Some(2),
)); ));
shr_instr.execute(&mut cpu); shr_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg1), 0b1010); assert_eq!(cpu.get(Register::Rg1), 0b1010);
} }
@@ -389,7 +441,9 @@ fn test_shift_left_with_register() {
None, None,
)); ));
shl_instr.execute(&mut cpu); shl_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg1), 0b101_0000); assert_eq!(cpu.get(Register::Rg1), 0b101_0000);
} }
@@ -403,7 +457,9 @@ fn test_load_lower_immediate() {
None, None,
)); ));
lli_instr.execute(&mut cpu); lli_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg1), 0x0000_1234); assert_eq!(cpu.get(Register::Rg1), 0x0000_1234);
} }
@@ -418,7 +474,9 @@ fn test_load_upper_immediate() {
None, None,
)); ));
lui_instr.execute(&mut cpu); lui_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg1), 0x1234_5678); assert_eq!(cpu.get(Register::Rg1), 0x1234_5678);
} }
@@ -430,7 +488,9 @@ fn test_jump_unconditional() {
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(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Pcx), 0x1100); assert_eq!(cpu.get(Register::Pcx), 0x1100);
assert_ne!(cpu.get(Register::Pcx), initial_pc); assert_ne!(cpu.get(Register::Pcx), initial_pc);
} }
@@ -444,7 +504,9 @@ fn test_jump_equal_when_flag_set() {
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(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Pcx), 0x1100); assert_eq!(cpu.get(Register::Pcx), 0x1100);
} }
@@ -458,7 +520,9 @@ fn test_jump_equal_when_flag_not_set() {
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(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Pcx), initial_pc); assert_eq!(cpu.get(Register::Pcx), initial_pc);
} }
@@ -467,7 +531,9 @@ 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);
} }
@@ -484,7 +550,9 @@ fn test_nand_instruction() {
None, None,
)); ));
nand_instr.execute(&mut cpu); nand_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg3), !0b1000); assert_eq!(cpu.get(Register::Rg3), !0b1000);
} }
@@ -501,7 +569,9 @@ fn test_nor_instruction() {
None, None,
)); ));
nor_instr.execute(&mut cpu); nor_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg3), !0b1110); assert_eq!(cpu.get(Register::Rg3), !0b1110);
} }
@@ -518,6 +588,8 @@ fn test_xnor_instruction() {
None, None,
)); ));
xnor_instr.execute(&mut cpu); xnor_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.get(Register::Rg3), !0b0110); assert_eq!(cpu.get(Register::Rg3), !0b0110);
} }
+58 -22
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: {}",
+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);
+77 -137
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,
@@ -199,38 +197,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 +218,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()
@@ -526,7 +465,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 +506,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());
+8 -3
View File
@@ -1,6 +1,9 @@
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 +23,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 +45,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}: "));
+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();
+294
View File
@@ -0,0 +1,294 @@
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()
.map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
}
"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;
+6 -1
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()
+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
} }
+35 -17
View File
@@ -5,7 +5,35 @@
include print "./lib/print.dsa" include print "./lib/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