39 Commits

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

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

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

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

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

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

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

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

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

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

Implements better error conversions and threading error handling for lock failures and panics.
2025-06-25 16:50:17 +01:00
nullndvoid 4e5db58a84 assembler: start refactoring/rewriting tokeniser 2025-06-25 14:48:45 +01:00
nullndvoid 11a57eab51 assembler: apply clippy lints 2025-06-25 14:33:48 +01:00
nullndvoid 20a7d42adb assembler: we failing DSA with this one 2025-06-25 14:31:53 +01:00
nullndvoid 9232f2ccab assembler: great leap forwards (more like the Cultural Revolution) 2025-06-25 03:26:50 +01:00
nullndvoid ce76820b6d assembler: begin wrangling 2025-06-25 02:25:46 +01:00
nullndvoid f72f36cd47 assembler: save currently broken refactors, its simpler to wipe then rebuild the assembler 2025-06-25 02:19:00 +01:00
nullndvoid 11ba09ab43 assembler: broke everything, currently modularising 2025-06-24 23:19:20 +01:00
nullndvoid 65efa8d423 misc: fix some clippy errors 2025-06-24 22:15:51 +01:00
nullndvoid ebae99811b misc: get rid of some errors from Cargo lol 2025-06-24 22:10:55 +01:00
zxq5 77331f65ab idk, i refactored some stuff ig 2025-06-24 22:10:55 +01:00
zxq5 6f2bb477ac finished the interpreter 2025-06-24 22:09:55 +01:00
zxq5 d87bf6bbb0 progress on debugging bf.dsa 2025-06-24 22:09:55 +01:00
zxq5 449612ac19 added step(n) feature to emulator, allowing for stepping n instructions at a time 2025-06-24 22:09:55 +01:00
zxq5 987c2b4b9a updated dependencies 2025-06-24 22:09:55 +01:00
zxq5 a55dfe616e finished refactor of emulator - started on loader (needs significant changes before functional in the way that I would like) 2025-06-24 22:09:55 +01:00
zxq5 2c44f48232 added error handling to emulator 2025-06-24 22:09:55 +01:00
nullndvoid 00a28e7711 elf: will start using clap to parse assembler arguments for CLI
I am tired af for some reason
2025-06-23 21:30:48 +01:00
66 changed files with 4109 additions and 3029 deletions
+11
View File
@@ -0,0 +1,11 @@
[build]
rustc-wrapper = "sccache"
# Enable to cut unused deps.
# rustflags = ["-D", "unused-crate-dependencies"]
[future-incompat-report]
frequency = "always"
[profile.profiling]
inherits = "release"
debug = true
Generated
+211 -27
View File
@@ -129,6 +129,15 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "android-activity"
version = "0.6.0"
@@ -165,6 +174,56 @@ dependencies = [
"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]]
name = "arboard"
version = "3.5.0"
@@ -216,9 +275,12 @@ dependencies = [
name = "assembler"
version = "0.2.0"
dependencies = [
"clap",
"common",
"num_cpus",
"regex",
"threadpool",
"uuid",
]
[[package]]
@@ -424,9 +486,9 @@ dependencies = [
[[package]]
name = "autocfg"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bit-set"
@@ -603,6 +665,46 @@ dependencies = [
"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]]
name = "clipboard-win"
version = "5.4.0"
@@ -622,6 +724,12 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "colorful"
version = "0.3.2"
@@ -641,6 +749,9 @@ dependencies = [
[[package]]
name = "common"
version = "0.2.0"
dependencies = [
"object",
]
[[package]]
name = "concurrent-queue"
@@ -1091,12 +1202,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.12"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -1601,6 +1712,12 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "1.0.15"
@@ -1641,9 +1758,9 @@ dependencies = [
[[package]]
name = "jpeg-decoder"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07"
[[package]]
name = "js-sys"
@@ -1674,9 +1791,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "libc"
version = "0.2.173"
version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "libloading"
@@ -1909,18 +2026,19 @@ dependencies = [
[[package]]
name = "num_enum"
version = "0.7.3"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a"
dependencies = [
"num_enum_derive",
"rustversion",
]
[[package]]
name = "num_enum_derive"
version = "0.7.3"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@@ -2208,12 +2326,31 @@ dependencies = [
"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]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "option-ext"
version = "0.2.0"
@@ -2435,9 +2572,9 @@ dependencies = [
[[package]]
name = "profiling"
version = "1.0.16"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d"
checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
[[package]]
name = "quick-xml"
@@ -2495,9 +2632,9 @@ dependencies = [
[[package]]
name = "r-efi"
version = "5.2.0"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
@@ -2564,6 +2701,35 @@ dependencies = [
"thiserror 2.0.12",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "renderdoc-sys"
version = "1.1.0"
@@ -2582,6 +2748,12 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "rustix"
version = "0.38.44"
@@ -2831,6 +3003,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.26.3"
@@ -2855,9 +3033,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.103"
version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
"proc-macro2",
"quote",
@@ -3046,9 +3224,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.29"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662"
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [
"proc-macro2",
"quote",
@@ -3137,6 +3315,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.17.0"
@@ -3987,9 +4171,9 @@ checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d"
[[package]]
name = "xcursor"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61"
checksum = "635887f4315a33cb714eb059bdbd7c1c92bfa71bc5b9d5115460502f788c2ab5"
[[package]]
name = "xdg-home"
@@ -4151,18 +4335,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.25"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.25"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [
"proc-macro2",
"quote",
+13
View File
@@ -1,3 +1,5 @@
cargo-features = ["codegen-backend"]
[workspace]
members = ["emulator", "common", "assembler", "dsa_editor"]
resolver = "3"
@@ -6,3 +8,14 @@ resolver = "3"
version = "0.2.0"
edition = "2024"
authors = ["zxq5", "nullndvoid"]
[profile.dev]
codegen-backend = "cranelift"
panic = "abort" # Cranelift does not support stack unwinds.
lto = false
debug = true
incremental = false # sccache does not support caching incremental crates.
[profile.release]
incremental = true
lto = "fat"
+3
View File
@@ -13,6 +13,9 @@ name = "assembler"
path = "src/lib.rs"
[dependencies]
clap = { version = "4.5.40", features = ["derive"] }
common = { path = "../common" }
num_cpus = "1.17.0"
regex = "1.11.1"
threadpool = "1.8.1"
uuid = { version = "1.17.0", features = ["v4"] }
+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.
+21
View File
@@ -0,0 +1,21 @@
use clap::{Parser, ValueEnum};
#[derive(Debug, Parser, Default)]
pub struct Args {
/// The output format to assemble to. Currently just ELF or a flat binary.
#[arg(value_enum)]
output_format: Option<OutputFormat>,
/// Whether the relocatable object files should be statically linked into a single
/// executable or library.
link: bool,
}
#[derive(Debug, Clone, Copy, ValueEnum, Default)]
/// The executable format the output should take.
pub enum OutputFormat {
/// An ELF file.
#[default]
Elf,
/// A flat binary file.
Flat,
}
-264
View File
@@ -1,264 +0,0 @@
use std::{
collections::HashSet,
fs,
path::{self, Path, PathBuf},
sync::{Arc, Mutex},
thread::{self, JoinHandle},
};
use crate::assembler::{AssembleError, Token, expand_pseudo_ops, lexer, quick_hash};
use crate::assembler::{Node, Parser, resolve_dependencies};
use crate::util::logging::Logger;
// pub fn new_assemble(path: &Path) {
// let program = Program::new();
// let program_ref = ProgramRef::new(program);
// let task = Module::build(path.to_path_buf(), program_ref.clone());
// program_ref.add_task(task);
// // wait on all tasks to finish
// for task in program_ref.get_tasks() {
// let module = task.module.join().unwrap();
// program_ref.add_module(module);
// }
// }
pub struct Program {
pub main_path: PathBuf,
registry: HashSet<u64>,
modules: Vec<Module>,
tasks: Vec<Task>,
logger: Logger,
}
impl Program {
#[must_use]
pub fn new() -> Self {
Self {
registry: HashSet::new(),
modules: Vec::new(),
tasks: Vec::new(),
main_path: PathBuf::new(),
logger: Logger::new(),
}
}
pub fn add_task(&mut self, task: Task) {
self.tasks.push(task);
}
}
impl Default for Program {
fn default() -> Self {
Self::new()
}
}
pub struct ProgramRef {
program: Arc<Mutex<Program>>,
}
impl ProgramRef {
#[must_use]
pub fn new(program: Program) -> Self {
Self {
program: Arc::new(Mutex::new(program)),
}
}
pub fn register(&self, path: &Path) {
self.program
.lock()
.expect("Failed to acquire program lock")
.registry
.insert(quick_hash(path));
}
#[must_use]
pub fn is_registered(&self, path: &Path) -> bool {
self.program
.lock()
.expect("Failed to acquire program lock")
.registry
.contains(&quick_hash(path))
}
// pub fn get_tasks(&self) -> Vec<&Task> {
// self.program.lock().unwrap().tasks.iter().collect()
// }
pub fn add_task(&self, task: Task) {
self.program
.lock()
.expect("Failed to acquire program lock")
.add_task(task);
}
pub fn add_module(&self, module: Module) {
self.program
.lock()
.expect("Failed to acquire program lock")
.modules
.push(module);
}
pub fn log(&self, message: &str) {
self.program
.lock()
.expect("Failed to acquire program lock")
.logger
.log(message);
}
}
impl Clone for ProgramRef {
fn clone(&self) -> Self {
Self {
program: self.program.clone(),
}
}
}
pub struct Module {
pub path: PathBuf,
pub hash: u64,
pub nodes: Vec<Node>,
program: ProgramRef,
}
impl Module {
#[must_use]
pub const fn new(
path: PathBuf,
hash: u64,
nodes: Vec<Node>,
program: ProgramRef,
) -> Self {
Self {
path,
hash,
nodes,
program,
}
}
pub fn build(path: PathBuf, program: ProgramRef) -> Result<Task, AssembleError> {
// Spawn a thread that creates the main function and executes the lexer and parser.
let handle = thread::spawn(move || {
let mut module =
Self::new(path.clone(), quick_hash(&path), Vec::new(), program.clone());
match module.lex() {
Ok(tokens) => {
module.parse(tokens);
module.expand();
module.prepare_dependencies();
module
}
Err(why) => {
eprintln!(
"Error building program at path `{}`: {why}",
path.display()
);
// TODO: Find a way to make this work without panicking.
unreachable!()
}
}
});
Ok(Task { module: handle })
}
fn lex(&self) -> Result<Vec<Token>, AssembleError> {
if let Ok(path) = self.path.canonicalize() {
self.program.log(&format!(
"{:20} {:20} [{}]",
"Building",
self.get_filename(),
path.display()
));
}
let src = fs::read_to_string(&self.path)
.map_err(|_| AssembleError::InvalidFile(self.path.clone()))?;
let file_hash = quick_hash(&self.path);
self.program
.log(&format!("{:20} {:20}", "Tokenising", self.get_filename()));
lexer::lexer(src, file_hash)
}
fn parse(&mut self, tokens: Vec<Token>) -> Result<(), AssembleError> {
self.program
.log(&format!("{:20} {:20}", "Parsing", self.get_filename()));
let parsed = Parser::parse_nodes(tokens)?;
self.nodes = parsed;
Ok(())
}
fn expand(&mut self) -> Result<(), AssembleError> {
self.program
.log(&format!("{:20} {:20}", "Expanding", self.get_filename()));
let expanded = expand_pseudo_ops(self.nodes.clone(), self.hash)?;
self.nodes = expanded;
Ok(())
}
fn prepare_dependencies(&self) -> Result<(), AssembleError> {
let nodes = resolve_dependencies(
self.nodes.clone(),
self.path.parent().expect("File should have a parent path!"),
)?;
let dependencies = Parser::get_dependencies(&nodes, &self.path)?;
for dep in dependencies {
if self.program.is_registered(&dep) {
// we have already built this module!
continue;
}
self.program.register(&dep);
// create new module
// add the task to the program
match Self::build(dep, self.program.clone()) {
Ok(task) => self.program.add_task(task),
Err(why) => {
eprintln!("Error building program: {why}");
}
}
}
Ok(())
}
/// Gets the filename from a [`PathBuf`].
fn get_filename(&self) -> &str {
self.path
.file_name()
.and_then(|f| f.to_str())
.unwrap_or_default()
}
/// Gets the parent filepath from a [`PathBuf`].
fn get_parent(&self) -> &str {
self.path
.parent()
.and_then(|f| f.to_str())
.unwrap_or_default()
}
}
pub struct Task {
module: JoinHandle<Module>,
}
-351
View File
@@ -1,351 +0,0 @@
use common::{args, prelude::*};
use crate::assembler::model::{Node, Opcode};
use crate::{assembler::AssembleError, expect_token};
fn log(message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}");
}
pub fn codegen(nodes: Vec<Node>) -> Result<Vec<Instruction>, AssembleError> {
let mut instructions = vec![];
for node in nodes {
instructions.push(
build_instruction(&node)
.unwrap_or_else(|_| panic!("Failed to build instruction: {node:?}")),
);
}
println!("------------------------");
log("Compilation Success ✅");
Ok(instructions)
}
fn build_instruction(node: &Node) -> Result<Instruction, AssembleError> {
let opcode = node.opcode();
let args = node.args();
match opcode {
Opcode::Nop => Ok(Instruction::Nop),
Opcode::Mov | Opcode::Movs => build_mov_instruction(opcode, &args),
Opcode::Ldb
| Opcode::Ldw
| Opcode::Ldh
| Opcode::Ldbs
| Opcode::Ldhs
| Opcode::Stb
| Opcode::Stw
| Opcode::Sth => build_memory_instruction(opcode, &args),
Opcode::Lli | Opcode::Lui => build_load_immediate_instruction(opcode, &args),
Opcode::Jmp
| Opcode::Jeq
| Opcode::Jne
| Opcode::Jgt
| Opcode::Jge
| Opcode::Jlt
| Opcode::Jle => build_jump_instruction(opcode, &args),
Opcode::Cmp => build_compare_instruction(&args),
Opcode::Inc | Opcode::Dec => build_inc_dec_instruction(opcode, &args),
Opcode::Shl | Opcode::Shr => build_shift_instruction(opcode, &args),
Opcode::Add
| Opcode::Sub
| Opcode::And
| Opcode::Or
| Opcode::Xor
| Opcode::Nand
| Opcode::Nor
| Opcode::Xnor => build_arithmetic_instruction(opcode, &args),
Opcode::AddI | Opcode::SubI => {
build_arithmetic_immediate_instruction(opcode, &args)
}
Opcode::Not => build_not_instruction(&args),
Opcode::Int => build_interrupt_instruction(&args),
Opcode::Irt => Ok(Instruction::IntReturn),
Opcode::Hlt => Ok(Instruction::Halt),
Opcode::Data => build_data_instruction(&args),
Opcode::Segment => build_segment_instruction(&args),
// These pseudo-instructions should have been expanded!
Opcode::Db
| Opcode::Dh
| Opcode::Dw
| Opcode::Resb
| Opcode::Resh
| Opcode::Resw
| Opcode::Push
| Opcode::Pop
| Opcode::Lwi
| Opcode::Include
| Opcode::Call
| Opcode::Return
| Opcode::Pusha
| Opcode::Popa => Err(AssembleError::InvalidArg),
}
}
fn build_mov_instruction(
opcode: Opcode,
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(src_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(dest_token) = args.get(1) else {
return Err(AssembleError::MissingArgument(1));
};
let src = expect_token!(src_token, Register)?;
let dest = expect_token!(dest_token, Register)?;
match opcode {
Opcode::Mov => Ok(Instruction::Mov(args!(R, sr1: src, dr: dest))),
Opcode::Movs => Ok(Instruction::MovSigned(args!(R, sr1: src, dr: dest))),
_ => unreachable!(),
}
}
fn build_memory_instruction(
opcode: Opcode,
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(base_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(dest_token) = args.get(1) else {
return Err(AssembleError::MissingArgument(1));
};
let Some(offset_token) = args.get(2) else {
return Err(AssembleError::MissingArgument(2));
};
let base = expect_token!(base_token, Register)?;
let dest = expect_token!(dest_token, Register)?;
let offset = expect_token!(offset_token, Immediate)?;
let instruction_args = args!(I, immediate: offset as u16, r1: base, r2: dest);
match opcode {
Opcode::Ldb => Ok(Instruction::LoadByte(instruction_args)),
Opcode::Ldw => Ok(Instruction::LoadWord(instruction_args)),
Opcode::Ldh => Ok(Instruction::LoadHalfword(instruction_args)),
Opcode::Ldbs => Ok(Instruction::LoadByteSigned(instruction_args)),
Opcode::Ldhs => Ok(Instruction::LoadHalfwordSigned(instruction_args)),
Opcode::Stb => Ok(Instruction::StoreByte(instruction_args)),
Opcode::Stw => Ok(Instruction::StoreWord(instruction_args)),
Opcode::Sth => Ok(Instruction::StoreHalfword(instruction_args)),
_ => unreachable!(),
}
}
fn build_load_immediate_instruction(
opcode: Opcode,
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(value_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(dest_token) = args.get(1) else {
return Err(AssembleError::MissingArgument(1));
};
let value = expect_token!(value_token, Immediate)?;
let dest = expect_token!(dest_token, Register)?;
match opcode {
Opcode::Lli => {
let instruction_args = args!(I, immediate: value as u16, r1: dest);
Ok(Instruction::LoadLowerImmediate(instruction_args))
}
Opcode::Lui => {
let upper_value = value >> 16;
let instruction_args = args!(I, immediate: upper_value as u16, r1: dest);
Ok(Instruction::LoadUpperImmediate(instruction_args))
}
_ => unreachable!(),
}
}
fn build_jump_instruction(
opcode: Opcode,
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(address_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(offset_token) = args.get(1) else {
return Err(AssembleError::MissingArgument(1));
};
let address = expect_token!(address_token, Immediate)?;
let offset = expect_token!(offset_token, Register)?;
let instruction_args = args!(I, immediate: address as u16, r1: offset);
match opcode {
Opcode::Jmp => Ok(Instruction::Jump(instruction_args)),
Opcode::Jeq => Ok(Instruction::JumpEq(instruction_args)),
Opcode::Jne => Ok(Instruction::JumpNeq(instruction_args)),
Opcode::Jgt => Ok(Instruction::JumpGt(instruction_args)),
Opcode::Jge => Ok(Instruction::JumpGe(instruction_args)),
Opcode::Jlt => Ok(Instruction::JumpLt(instruction_args)),
Opcode::Jle => Ok(Instruction::JumpLe(instruction_args)),
_ => unreachable!(),
}
}
fn build_compare_instruction(
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(left_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(right_token) = args.get(1) else {
return Err(AssembleError::MissingArgument(1));
};
let left = expect_token!(left_token, Register)?;
let right = expect_token!(right_token, Register)?;
Ok(Instruction::Compare(args!(R, sr1: left, sr2: right)))
}
fn build_inc_dec_instruction(
opcode: Opcode,
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(reg_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let reg = expect_token!(reg_token, Register)?;
match opcode {
Opcode::Inc => Ok(Instruction::Increment(args!(R, sr1: reg))),
Opcode::Dec => Ok(Instruction::Decrement(args!(R, sr1: reg))),
_ => unreachable!(),
}
}
fn build_shift_instruction(
opcode: Opcode,
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(reg_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(amount_token) = args.get(1) else {
return Err(AssembleError::MissingArgument(1));
};
let reg = expect_token!(reg_token, Register)?;
let amount = expect_token!(amount_token, Immediate)? as u8;
match opcode {
Opcode::Shl => Ok(Instruction::ShiftLeft(args!(R, sr1: reg, shamt: amount))),
Opcode::Shr => Ok(Instruction::ShiftRight(args!(R, sr1: reg, shamt: amount))),
_ => unreachable!(),
}
}
fn build_arithmetic_instruction(
opcode: Opcode,
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(left_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(right_token) = args.get(1) else {
return Err(AssembleError::MissingArgument(1));
};
let Some(dest_token) = args.get(2) else {
return Err(AssembleError::MissingArgument(2));
};
let left = expect_token!(left_token, Register)?;
let right = expect_token!(right_token, Register)?;
let dest = expect_token!(dest_token, Register)?;
let instruction_args = args!(R, sr1: left, sr2: right, dr: dest);
match opcode {
Opcode::Add => Ok(Instruction::Add(instruction_args)),
Opcode::Sub => Ok(Instruction::Sub(instruction_args)),
Opcode::And => Ok(Instruction::And(instruction_args)),
Opcode::Or => Ok(Instruction::Or(instruction_args)),
Opcode::Xor => Ok(Instruction::Xor(instruction_args)),
Opcode::Nand => Ok(Instruction::Nand(instruction_args)),
Opcode::Nor => Ok(Instruction::Nor(instruction_args)),
Opcode::Xnor => Ok(Instruction::Xnor(instruction_args)),
_ => unreachable!(),
}
}
fn build_arithmetic_immediate_instruction(
opcode: Opcode,
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(reg_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(immediate_token) = args.get(1) else {
return Err(AssembleError::MissingArgument(1));
};
let Some(dest_token) = args.get(2) else {
return Err(AssembleError::MissingArgument(2));
};
let reg = expect_token!(reg_token, Register)?;
let immediate = expect_token!(immediate_token, Immediate)? as u16;
let dest = expect_token!(dest_token, Register)?;
let instruction_args = args!(I, immediate: immediate, r1: reg, r2: dest);
match opcode {
Opcode::AddI => Ok(Instruction::AddImmediate(instruction_args)),
Opcode::SubI => Ok(Instruction::SubImmediate(instruction_args)),
_ => unreachable!(),
}
}
fn build_not_instruction(
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(reg_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(dest_token) = args.get(1) else {
return Err(AssembleError::MissingArgument(1));
};
let reg = expect_token!(reg_token, Register)?;
let dest = expect_token!(dest_token, Register)?;
Ok(Instruction::Not(args!(R, sr1: reg, dr: dest)))
}
fn build_interrupt_instruction(
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(code_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let code = expect_token!(code_token, Immediate)? as u8;
Ok(Instruction::Interrupt(Interrupt::Software(code)))
}
fn build_data_instruction(
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(immediate_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let immediate = expect_token!(immediate_token, Immediate)?;
Ok(Instruction::Data(immediate))
}
fn build_segment_instruction(
args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> {
let Some(immediate_token) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let immediate = expect_token!(immediate_token, Immediate)?;
Ok(Instruction::Segment(immediate))
}
-368
View File
@@ -1,368 +0,0 @@
use common::prelude::Register;
use crate::assembler::model::{Node, Opcode, Token};
use crate::{assembler::AssembleError, expect_token, expect_type, node};
pub fn expand_pseudo_ops(
mut nodes: Vec<Node>,
module: u64,
) -> Result<Vec<Node>, AssembleError> {
let mut result = Vec::<Node>::with_capacity(nodes.len());
for node in &mut nodes {
if try_expand(node.clone(), &mut result, module).is_err() {
result.push(node.clone());
}
}
Ok(result)
}
fn try_expand(
node: Node,
result: &mut Vec<Node>,
_module: u64,
) -> Result<(), AssembleError> {
match node.opcode() {
Opcode::Push => expand_push(&node, result)?,
Opcode::Pop => expand_pop(&node, result)?,
Opcode::Pusha => expand_pusha(&node, result)?,
Opcode::Popa => expand_popa(&node, result)?,
Opcode::Call => expand_call(&node, result)?,
Opcode::Return => expand_return(&node, result),
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw => {
expand_ldx(&node, result)?;
}
Opcode::Stb | Opcode::Sth | Opcode::Stw => expand_stx(&node, result)?,
Opcode::Lwi => expand_lwi(&node, result)?,
Opcode::Resb | Opcode::Resh | Opcode::Resw => expand_resx(&node, result)?,
Opcode::Db | Opcode::Dh | Opcode::Dw => expand_dx(&node, result)?,
_ => result.push(node),
}
Ok(())
}
fn expand_push(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let label = current.label();
let Ok(arg0) = current.arg(0) else {
return Err(AssembleError::Generic);
};
let reg = expect_type!(arg0, Register)?;
let spr = Token::Register(Register::Spr);
nodes.extend(vec![
node!(label, Opcode::SubI, spr, 4, spr),
node!(None, Opcode::Stw, reg, spr, 0),
]);
Ok(())
}
fn expand_pusha(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let label = current.label();
let Ok(arg0) = current.arg(0) else {
return Err(AssembleError::Generic);
};
let count = expect_token!(arg0, Immediate)?;
let spr = Token::Register(Register::Spr);
let registers: Vec<Register> = Register::general();
nodes.push(node!(
label,
Opcode::SubI,
spr,
Token::Immediate(count * 4),
spr
));
nodes.extend((0..count).rev().map(|i| {
node!(
None,
Opcode::Stw,
Token::Register(registers[i as usize]),
spr,
Token::Immediate(i * 4)
)
}));
Ok(())
}
fn expand_popa(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let label = current.label();
let Ok(arg0) = current.arg(0) else {
return Err(AssembleError::Generic);
};
let count = expect_token!(arg0, Immediate)?;
let spr = Token::Register(Register::Spr);
let registers: Vec<Register> = Register::general();
nodes.extend((0..count).rev().map(|i| {
node!(
{ if i == 0 { label.clone() } else { None } },
Opcode::Ldw,
spr,
Token::Register(registers[i as usize]),
Token::Immediate(i * 4)
)
}));
nodes.push(node!(
None,
Opcode::AddI,
spr,
Token::Immediate(count * 4),
spr
));
Ok(())
}
fn expand_call(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let label = current.label();
let Ok(arg0) = current.arg(0) else {
return Err(AssembleError::Generic);
};
let addr = expect_type!(arg0, Symbol)?;
let spr = Token::Register(Register::Spr);
let pcx = Token::Register(Register::Pcx);
let zero = Token::Register(Register::Zero);
nodes.extend(vec![
node!(label, Opcode::SubI, spr, 4, spr),
node!(None, Opcode::Stw, pcx, spr, 0),
node!(None, Opcode::Jmp, addr, zero),
]);
Ok(())
}
fn expand_return(current: &Node, nodes: &mut Vec<Node>) {
let label = current.label();
let spr = Token::Register(Register::Spr);
let ret = Token::Register(Register::Ret);
nodes.extend(vec![
node!(label, Opcode::Ldw, spr, ret, 0),
node!(None, Opcode::AddI, spr, 4, spr),
node!(None, Opcode::Jmp, 4, ret),
]);
}
fn expand_pop(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let label = current.label();
let Ok(arg0) = current.arg(0) else {
return Err(AssembleError::Generic);
};
let reg = expect_type!(arg0, Register)?;
let spr = Token::Register(Register::Spr);
nodes.extend(vec![
node!(label, Opcode::Ldw, spr, reg, 0),
node!(None, Opcode::AddI, spr, 4, spr),
]);
Ok(())
}
fn expand_ldx(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let opcode = current.opcode();
let args: Vec<Token> = current.args().into_iter().take(3).collect();
let Some(name) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(reg) = args.get(1) else {
return Err(AssembleError::MissingArgument(1));
};
let Some(offset) = args.get(2) else {
return Err(AssembleError::MissingArgument(2));
};
let name = expect_type!(name, Symbol)?;
let reg = expect_type!(reg, Register)?;
let offset = expect_type!(offset, Immediate)?;
nodes.extend(vec![
node!(current.label(), Opcode::Lli, name, reg),
node!(None, Opcode::Lui, name, reg),
node!(None, opcode, reg, reg, offset),
]);
Ok(())
}
fn expand_stx(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let opcode = current.opcode();
let args: Vec<Token> = current.args().into_iter().take(3).collect();
let Some(base) = args.first() else {
return Err(AssembleError::MissingArgument(0));
};
let Some(dest) = args.get(1) else {
return Err(AssembleError::MissingArgument(1));
};
let Some(offset) = args.get(2) else {
return Err(AssembleError::MissingArgument(2));
};
let base = expect_type!(base, Register)?;
let dest = expect_type!(dest, Symbol)?;
let offset = expect_type!(offset, Immediate)?;
let temp = Token::Register(Register::Acc);
nodes.extend(vec![
node!(current.label(), Opcode::Lli, dest, temp),
node!(None, Opcode::Lui, dest, temp),
node!(None, opcode, base, temp, offset),
]);
Ok(())
}
fn expand_lwi(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let Ok(val) = current.arg(0) else {
return Err(AssembleError::MissingArgument(0));
};
let Ok(reg) = current.arg(1) else {
return Err(AssembleError::MissingArgument(1));
};
let val = expect_type!(val, Symbol, Immediate)?;
let reg = expect_type!(reg, Register)?;
nodes.extend(vec![
node!(current.label(), Opcode::Lli, val, reg),
node!(None, Opcode::Lui, val, reg),
]);
Ok(())
}
fn expand_resx(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let Ok(region_label) = current.arg(0) else {
return Err(AssembleError::MissingArgument(0));
};
let Ok(size) = current.arg(1) else {
return Err(AssembleError::MissingArgument(1));
};
let region_label = expect_token!(region_label, Symbol)?;
let size = expect_token!(size, Immediate)?;
let units_per = match current.opcode() {
Opcode::Resb => 4,
Opcode::Resh => 2,
Opcode::Resw => 1,
_ => unreachable!(),
};
let mut buffer = vec![];
// push the inital node with the label
for _ in 0..size.div_ceil(units_per) {
// push the rest of the nodes
buffer.push(node!(None, Opcode::Data, 0));
}
buffer[0].symbol = Some(region_label);
nodes.extend(buffer);
Ok(())
}
fn expand_dx(current: &Node, nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let Ok(region_label) = current.arg(0) else {
return Err(AssembleError::MissingArgument(0));
};
let region_label = expect_token!(region_label, Symbol)?;
let size = match current.opcode() {
Opcode::Db => 4,
Opcode::Dh => 2,
Opcode::Dw => 1,
_ => unreachable!(),
};
let mut buffer = vec![];
let mut args = current.args();
let _label = args.remove(0);
for word in process_dx_data(args, size)? {
buffer.push(node!(None, Opcode::Data, Token::Immediate(word)));
}
buffer[0].symbol = Some(region_label);
nodes.extend(buffer);
Ok(())
}
fn process_dx_data(args: Vec<Token>, size: usize) -> Result<Vec<u32>, AssembleError> {
assert!(matches!(size, 1 | 2 | 4));
let mut buffer = Vec::<u8>::new();
// Process each token
for token in args {
match token {
Token::StringLit(mut s) => {
s.push('\0');
// Split string into chars and write as bytes
for ch in s.chars() {
// Convert char to bytes (UTF-8 encoding)
let mut char_buf = [0u8; 4];
let char_bytes = ch.encode_utf8(&mut char_buf);
buffer.extend_from_slice(char_bytes.as_bytes());
}
}
Token::Immediate(value) => {
// Split u32 into bytes (little-endian)
buffer.extend_from_slice(&value.to_be_bytes());
}
_ => {
return Err(AssembleError::Generic);
}
}
// Pad buffer to alignment boundary with zeros
let remainder = buffer.len() % size;
if remainder != 0 {
let padding = size - remainder;
buffer.resize(buffer.len() + padding, 0);
}
}
// Convert byte buffer to u32 chunks
// Pad final buffer to u32 boundary if needed
let remainder = buffer.len() % 4;
if remainder != 0 {
let padding = 4 - remainder;
buffer.resize(buffer.len() + padding, 0);
}
// Convert bytes to u32s efficiently using chunks_exact
let result = buffer
.chunks_exact(4)
.map(|chunk| {
// Convert 4 bytes to u32 (little-endian)
u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])
})
.collect();
Ok(result)
}
-165
View File
@@ -1,165 +0,0 @@
use std::str::FromStr;
use crate::assembler::AssembleError;
use crate::assembler::model::{Module, Opcode, Symbol, Token};
use common::prelude::Register;
pub fn lexer(mut program: String, module: u64) -> Result<Vec<Token>, AssembleError> {
let mut tokens = Vec::new();
program = program.replace(',', "");
let lines = program.lines();
let mut literal = String::new();
for line in lines {
for token in line.split_whitespace() {
if token.starts_with("//") {
break;
}
if let Some(stripped) = token.strip_prefix('"') {
literal.push_str(stripped);
}
if !literal.is_empty() {
if !token.starts_with('"') {
literal.push(' ');
literal.push_str(token);
}
if token.ends_with('"') {
literal.pop(); // remove the closing quote
tokens.push(Token::StringLit(literal));
literal = String::new();
}
continue;
}
if let Some(token) = parse_register(token)? {
tokens.push(token);
} else if let Some(token) = parse_opcode(token)? {
tokens.push(token);
} else if let Some(token) = parse_hex(token)? {
tokens.push(token);
} else if let Some(token) = parse_octal(token)? {
tokens.push(token);
} else if let Some(token) = parse_binary(token)? {
tokens.push(token);
} else if let Some(token) = parse_decimal(token)? {
tokens.push(token);
} else if let Some(token) = parse_label(token, module)? {
tokens.push(token);
} else if let Some(token) = parse_symbol(token, module)? {
tokens.push(token);
} else {
return Err(AssembleError::Generic);
}
}
}
Ok(tokens)
}
pub fn parse_register(token: &str) -> Result<Option<Token>, AssembleError> {
Ok(Register::try_from(token).map(Token::Register).ok())
}
pub fn parse_opcode(token: &str) -> Result<Option<Token>, AssembleError> {
if Opcode::OPCODES.contains(&token) {
Ok(Some(Token::Opcode(Opcode::from_str(token).expect(
"Opcode::from_str failed for a valid opcode token",
))))
} else {
Ok(None)
}
}
pub fn parse_hex(token: &str) -> Result<Option<Token>, AssembleError> {
if (token.len() < 3) | !token.starts_with("0x") {
return Ok(None);
}
let Some(lit) = &token.get(2..) else {
return Err(AssembleError::InvalidArg);
};
u32::from_str_radix(lit, 16).map_or(Err(AssembleError::Generic), |value| {
Ok(Some(Token::Immediate(value)))
})
}
pub fn parse_octal(token: &str) -> Result<Option<Token>, AssembleError> {
if (token.len() < 3) | !token.starts_with("0o") {
return Ok(None);
}
let Some(lit) = &token.get(2..) else {
return Err(AssembleError::InvalidArg);
};
u32::from_str_radix(lit, 8).map_or(Err(AssembleError::Generic), |value| {
Ok(Some(Token::Immediate(value)))
})
}
pub fn parse_binary(token: &str) -> Result<Option<Token>, AssembleError> {
if (token.len() < 3) | !token.starts_with("0b") {
return Ok(None);
}
let Some(lit) = &token.get(2..) else {
return Err(AssembleError::InvalidArg);
};
u32::from_str_radix(lit, 2).map_or(Err(AssembleError::Generic), |value| {
Ok(Some(Token::Immediate(value)))
})
}
pub fn parse_decimal(token: &str) -> Result<Option<Token>, AssembleError> {
let Ok(tok) = token.parse::<u32>() else {
return Ok(None);
};
Ok(Some(Token::Immediate(tok)))
}
pub fn parse_label(token: &str, module: u64) -> Result<Option<Token>, AssembleError> {
if token.ends_with(':') {
Ok(Some(Token::Symbol(Symbol {
name: token[0..token.len() - 1].to_string(),
module: Module::Resolved(module),
})))
} else {
Ok(None)
}
}
pub fn parse_symbol(token: &str, module: u64) -> Result<Option<Token>, AssembleError> {
let Some(tokc) = token.chars().next() else {
return Err(AssembleError::Generic); // TODO: What is this error?
};
if tokc.is_numeric() {
return Ok(None);
}
let mut split = token.splitn(2, "::");
let Some(symbol1) = split.next() else {
return Err(AssembleError::InvalidArg);
};
let symbol1 = symbol1.to_string();
if let Some(symbol2) = split.next() {
Ok(Some(Token::Symbol(Symbol {
name: symbol2.to_string(),
module: Module::Unresolved(symbol1),
})))
} else {
Ok(Some(Token::Symbol(Symbol {
name: symbol1,
module: Module::Resolved(module),
})))
}
}
-139
View File
@@ -1,139 +0,0 @@
//! Macros used throughout the assembler
use crate::assembler::model::{Node, Opcode, Symbol, Token};
/// Parse DSA assembly code with optional formatting
///
/// # Examples
/// ```
/// // With formatting:
/// let nodes = dsa!(hash, "mov r1, {}", 42)?;
///
/// // Without formatting:
/// let nodes = dsa!(hash, "mov r1, 42")?;
/// ```
#[macro_export]
macro_rules! dsa {
// Version with formatting arguments
($hash:expr, $input:expr, $($args:expr),+) => {{
let input = format!($input, $($args),+);
let tokens = $crate::lexer::lexer(input, $hash)?;
let parsed = $crate::parser::Parser::parse_nodes(tokens)?;
parsed
}};
// Version without formatting
($hash:expr, $input:expr) => {{
let input = String::from($input);
let tokens = $crate::lexer::lexer(input, $hash)?;
let parsed = $crate::parser::Parser::parse_nodes(tokens)?;
parsed
}};
}
/// Creates a new Node with the given symbol, opcode, and tokens
#[macro_export]
macro_rules! node {
($symbol: expr, $opcode: expr, args: $tokens: expr) => {
$crate::assembler::model::Node::new($symbol.clone(), $opcode.clone(), $tokens.clone())
};
($symbol: expr, $opcode: expr, $($tokens: expr),+) => {
$crate::assembler::model::Node::new(
$symbol.clone(),
$opcode.clone(),
vec![$(node!(@convert_token $tokens)),+]
)
};
($symbol: expr, $opcode: expr) => {
$crate::assembler::model::Node::new(
$symbol.clone(),
$opcode.clone(),
Vec::new()
)
};
(@convert_token $token: literal) => {
$crate::assembler::model::Token::Immediate($token)
};
(@convert_token $token: expr) => {
$token.clone()
};
}
/// Extracts a specific token type from a token
#[macro_export]
macro_rules! expect_token {
($token:expr, Symbol) => {
match $token {
$crate::assembler::model::Token::Symbol(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::Symbol,
)),
}
};
($token:expr, Register) => {
match $token {
$crate::assembler::model::Token::Register(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::Register,
)),
}
};
($token:expr, Immediate) => {
match $token {
$crate::assembler::model::Token::Immediate(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::Immediate,
)),
}
};
($token:expr, StringLit) => {
match $token {
$crate::assembler::model::Token::StringLit(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::StringLit,
)),
}
};
($token:expr, Opcode) => {
match $token {
$crate::assembler::model::Token::Opcode(value) => Ok(value.clone()),
other => Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone(),
$crate::assembler::model::TokenType::Opcode,
)),
}
};
}
/// Checks if a token matches any of the specified types
#[macro_export]
macro_rules! expect_type {
($token:expr, $($variant:ident),+) => {{
let token = $token;
match &token {
$(
$crate::assembler::model::Token::$variant(_) => Ok(token.clone()),
)+
other => {
let expected_type = expect_type!(@get_first_type $($variant),+);
Err($crate::assembler::AssembleError::UnexpectedToken(
other.clone().clone(),
expected_type,
))
}
}
}};
(@get_first_type Symbol $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Symbol };
(@get_first_type Register $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Register };
(@get_first_type Immediate $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Immediate };
(@get_first_type StringLit $(, $rest:ident)*) => { $crate::assembler::model::TokenType::StringLit };
(@get_first_type Opcode $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Opcode };
}
-266
View File
@@ -1,266 +0,0 @@
#![allow(dead_code, unused)]
use std::{
collections::HashSet,
fmt, fs,
hash::{DefaultHasher, Hash, Hasher},
path::{Path, PathBuf},
sync::{Arc, Mutex, mpsc},
thread,
};
use common::prelude::Instruction;
// TODO: Use an actual logging or tracing library for pretty (scoped) output.
fn log(message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}");
}
// Module declarations
#[macro_use]
pub mod macros;
#[allow(clippy::module_inception)]
pub mod assembler;
pub mod codegen;
pub mod expand;
pub mod lexer;
pub mod model;
pub mod parser;
pub mod resolver;
// Re-exports
pub use self::{
codegen::codegen,
expand::expand_pseudo_ops,
lexer::lexer,
model::{Module, Node, Opcode, Symbol, Token, TokenType},
parser::{Parser, Program},
resolver::{create_sections, resolve_dependencies, resolve_symbols},
};
use crate::util::logging::{Entry, Logger};
pub struct CompilerEngine {
result_tx: mpsc::Sender<Result<Vec<Instruction>, AssembleError>>,
result_rx: Option<mpsc::Receiver<Result<Vec<Instruction>, AssembleError>>>,
is_running: bool,
}
impl CompilerEngine {
#[must_use]
pub fn new() -> Self {
let (tx, rx) = mpsc::channel();
Self {
result_tx: tx,
result_rx: Some(rx),
is_running: false,
}
}
/// Start the compilation process in a separate thread
pub fn start_compilation(&mut self, src: &Path) {
if self.is_running {
return;
}
let src = src.to_path_buf();
let tx = self.result_tx.clone();
thread::spawn(move || {
let result = assemble(&src);
tx.send(result)
.expect("Failed to send compilation result from worker thread");
});
self.is_running = true;
}
/// Check if compilation is complete and get the result
pub fn try_get_result(&mut self) -> Option<Result<Vec<Instruction>, AssembleError>> {
if !self.is_running {
return None;
}
match self
.result_rx
.as_ref()
.expect("result_rx should be Some while compilation is running")
.try_recv()
{
Ok(result) => {
self.is_running = false;
Some(result)
}
Err(mpsc::TryRecvError::Empty) => None,
Err(mpsc::TryRecvError::Disconnected) => {
self.is_running = false;
Some(Err(AssembleError::Generic))
}
}
}
/// Block until compilation is complete and return the result
pub fn wait_for_result(&mut self) -> Result<Vec<Instruction>, AssembleError> {
if !self.is_running {
return Err(AssembleError::Generic);
}
if let Ok(result) = self
.result_rx
.take()
.expect("result_rx should be Some while waiting for compilation result")
.recv()
{
self.is_running = false;
result
} else {
self.is_running = false;
Err(AssembleError::Generic)
}
}
}
fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
let mut modules = HashSet::new();
let mut program = Program::new();
let hash = quick_hash(src);
if modules.contains(&hash) {
return Ok(vec![]);
}
prepare_dependency(src, &mut modules, &mut program)?;
let mut nodes = program.nodes.clone();
create_sections(&mut nodes)?;
resolve_symbols(&mut nodes)?;
let instructions = codegen(nodes)?;
Ok(instructions)
}
impl Default for CompilerEngine {
fn default() -> Self {
Self::new()
}
}
fn prepare_dependency(
path: &Path,
modules: &mut HashSet<u64>,
program: &mut Program,
) -> Result<(), AssembleError> {
let filename = path
.file_name()
.and_then(|n| n.to_str())
.expect("Failed to get file name from path");
if let Ok(path) = path.canonicalize() {
log(&format!(
"{:20} {:20} [{}]",
"Building",
filename,
path.display()
));
}
let src = fs::read_to_string(path)
.map_err(|_| AssembleError::InvalidFile(path.to_path_buf()))?;
let file_hash = quick_hash(path);
log(&format!("{:20} {:20}", "Tokenising", filename));
let tokens = lexer::lexer(src, file_hash)?;
log(&format!("{:20} {:20}", "Parsing", filename));
let parsed = Parser::parse_nodes(tokens)?;
log(&format!("{:20} {:20}", "Resolving Deps", filename));
// Get the parent directory of the source file to use as the base directory
let base_dir = path
.parent()
.ok_or_else(|| AssembleError::InvalidFile(path.to_path_buf()))?;
let mut nodes = expand_pseudo_ops(parsed, file_hash)?;
nodes = resolve_dependencies(nodes, base_dir)?;
let deps = Parser::get_dependencies(&nodes, path)?;
log(&format!(
"{:20} {:20}",
"Expanding PseudoInstructions", filename
));
// add a section instruction
nodes.insert(
0,
node!(None, Opcode::Segment, Token::Immediate(file_hash as u32)),
);
for n in &nodes {
println!("{n}");
}
program.add_module(nodes);
for dep in deps {
log(&format!(
"{:20} {:20}",
"Including",
dep.file_name()
.and_then(|f| f.to_str())
.expect("Dependency path has no file name or is not valid UTF-8")
));
let dep_hash = quick_hash(&dep);
if modules.insert(dep_hash) {
prepare_dependency(dep.as_path(), modules, program)?;
}
}
Ok(())
}
#[derive(Debug, Clone)]
pub enum AssembleError {
Generic,
UnexpectedEof,
InvalidFile(PathBuf),
UnexpectedToken(Token, TokenType),
InvalidArg,
UndefinedSymbol(Symbol),
/// Contains the nth element missing from the instruction.
MissingArgument(u8),
}
impl fmt::Display for AssembleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Generic => write!(f, "Generic error"),
Self::UnexpectedToken(tok, expected) => {
write!(f, "Unexpected token {tok:?}, expected {expected:?}")
}
Self::UnexpectedEof => write!(f, "Unexpected end of file"),
Self::InvalidFile(path) => write!(f, "Invalid file `{}`", path.display()),
Self::InvalidArg => write!(f, "Invalid argument"),
Self::UndefinedSymbol(symbol) => {
write!(f, "Undefined symbol {symbol}")
}
Self::MissingArgument(n) => {
write!(f, "Missing argument #{n} from instruction arguments.")
}
}
}
}
fn quick_hash(value: &Path) -> u64 {
let mut hasher = DefaultHasher::new();
value
.canonicalize()
.expect("Failed to canonicalize path for quick_hash")
.to_str()
.hash(&mut hasher);
hasher.finish()
}
-370
View File
@@ -1,370 +0,0 @@
use std::path::{Path, PathBuf};
use crate::{assembler::AssembleError, expect_token, expect_type, node};
use crate::assembler::model::{Node, Opcode, Token};
use common::prelude::*;
pub struct Parser {
tokens: Vec<Token>,
nodes: Vec<Node>,
}
#[derive(Debug)]
pub struct Program {
pub nodes: Vec<Node>,
}
impl Program {
#[must_use]
pub const fn new() -> Self {
Self { nodes: vec![] }
}
pub fn add_module(&mut self, module: Vec<Node>) {
self.nodes.extend(module);
}
pub fn parser(&mut self) -> Parser {
Parser {
tokens: vec![],
nodes: self.nodes.clone(),
}
}
}
impl Default for Program {
fn default() -> Self {
Self::new()
}
}
impl Parser {
pub fn parse_nodes(tokens: Vec<Token>) -> Result<Vec<Node>, AssembleError> {
let mut self_ = Self {
tokens: tokens.into_iter().rev().collect(),
nodes: vec![],
};
while !self_.tokens.is_empty() {
let ins = self_.parse_instruction()?;
self_.nodes.push(ins);
}
Ok(self_.nodes.clone())
}
pub fn get_dependencies(
nodes: &Vec<Node>,
source_path: &Path,
) -> Result<Vec<PathBuf>, AssembleError> {
let mut dependencies = Vec::new();
// Get the parent directory of the source file to use as the base directory
let base_dir = source_path
.parent()
.ok_or_else(|| AssembleError::InvalidFile(source_path.to_path_buf()))?;
for node in nodes {
if node.opcode() == Opcode::Include {
let path_str = expect_token!(
node.args().get(1).ok_or(AssembleError::Generic)?,
StringLit
)?;
let path = PathBuf::from(path_str);
// If the path is not absolute, make it relative to the base directory
let full_path = if path.is_absolute() {
path
} else {
base_dir.join(path)
};
dependencies.push(full_path);
}
}
Ok(dependencies)
}
#[expect(clippy::too_many_lines, clippy::cognitive_complexity)]
fn parse_instruction(&mut self) -> Result<Node, AssembleError> {
if self.tokens.is_empty() {
unreachable!();
}
// check if the Node starts with a label
let label = expect_token!(self.peek_next()?, Symbol).ok();
if label.is_some() {
self.tokens.pop();
}
let opcode = expect_token!(self.next()?, Opcode)?;
let args: Vec<Token>;
match opcode {
// R-type instructions
Opcode::Mov | Opcode::Movs => {
let reg1 = expect_type!(self.next()?, Register, Symbol)?;
let reg2 = expect_type!(self.next()?, Register, Symbol)?;
args = vec![reg1, reg2];
}
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw => {
let base = expect_type!(self.next()?, Register, Symbol)?;
let dest = expect_type!(self.next()?, Register)?;
let mut offset = Token::Immediate(0);
if let Ok(next) = self.peek_next() {
if expect_type!(next, Immediate).is_ok() {
offset = self.next()?;
}
}
args = vec![base, dest, offset];
}
Opcode::Stb | Opcode::Sth | Opcode::Stw => {
let base = expect_type!(self.next()?, Register)?;
let dest = expect_type!(self.next()?, Register, Symbol)?;
let mut offset = Token::Immediate(0);
if let Ok(next) = self.peek_next() {
if expect_type!(next, Immediate).is_ok() {
offset = self.next()?;
}
}
args = vec![base, dest, offset];
}
Opcode::Add
| Opcode::Sub
| Opcode::And
| Opcode::Or
| Opcode::Xor
| Opcode::Nand
| Opcode::Nor
| Opcode::Xnor => {
let src1 = expect_type!(self.next()?, Register, Symbol)?;
let src2 = expect_type!(self.next()?, Register, Symbol)?;
let dest = expect_type!(self.next()?, Register, Symbol)?;
args = vec![src1, src2, dest];
}
Opcode::Not | Opcode::Cmp => {
let reg1 = expect_type!(self.next()?, Register, Symbol)?;
let reg2 = expect_type!(self.next()?, Register, Symbol)?;
args = vec![reg1, reg2];
}
Opcode::Shl | Opcode::Shr => {
let reg = expect_type!(self.next()?, Register, Symbol)?;
let num = expect_type!(self.next()?, Immediate)?;
args = vec![reg, num];
}
Opcode::Inc | Opcode::Dec => {
let reg = expect_type!(self.next()?, Register, Symbol)?;
args = vec![reg];
}
Opcode::Include => {
let mod_name = expect_type!(self.next()?, Symbol)?;
let path = expect_type!(self.next()?, StringLit)?;
args = vec![mod_name, path];
}
// J-type instructions
Opcode::Jmp
| Opcode::Jeq
| Opcode::Jne
| Opcode::Jgt
| Opcode::Jge
| Opcode::Jlt
| Opcode::Jle => {
let imm = expect_type!(self.next()?, Immediate, Symbol)?;
let offset = match self.peek_next() {
Ok(token) => {
if expect_type!(token, Register).is_ok() {
self.next()?
} else {
Token::Register(Register::Zero)
}
}
Err(_) => Token::Register(Register::Zero),
};
args = vec![imm, offset];
}
Opcode::Call => {
let addr = expect_type!(self.next()?, Symbol)?;
args = vec![addr];
}
// I-type instructions
Opcode::Lui | Opcode::Lli | Opcode::Lwi => {
let imm = expect_type!(self.next()?, Immediate, Symbol)?;
let reg = expect_type!(self.next()?, Register)?;
args = vec![imm, reg];
}
// Immediate Arithmetic
Opcode::AddI | Opcode::SubI => {
let reg = expect_type!(self.next()?, Register)?;
let imm = expect_type!(self.next()?, Immediate)?;
let reg2 = if expect_type!(self.peek_next()?, Register).is_ok() {
self.next()?
} else {
reg.clone()
};
args = vec![reg, imm, reg2];
}
// D-type pseudoinstructions (data definition)
Opcode::Resb | Opcode::Resh | Opcode::Resw => {
let name = expect_type!(self.next()?, Symbol)?;
let num = expect_type!(self.next()?, Immediate)?;
args = vec![name, num];
}
Opcode::Db | Opcode::Dh | Opcode::Dw => {
args = self.parse_data_definition(opcode)?;
}
// E-type pseudoinstructions (stack operations)
Opcode::Push | Opcode::Pop => {
let reg = expect_type!(self.next()?, Register, Symbol)?;
args = vec![reg];
}
Opcode::Pusha | Opcode::Popa => {
let count =
expect_type!(self.next()?, Immediate).unwrap_or(Token::Immediate(8));
args = vec![count];
}
// Special instructions
Opcode::Int => {
let val = expect_type!(self.next()?, Immediate)?;
args = vec![val];
}
// Instructions with no arguments
Opcode::Hlt | Opcode::Nop | Opcode::Irt | Opcode::Return => {
args = vec![];
}
Opcode::Data | Opcode::Segment => {
return Err(AssembleError::Generic);
}
}
Ok(node!(label, opcode, args: args))
}
fn parse_data_definition(
&mut self,
opcode: Opcode,
) -> Result<Vec<Token>, AssembleError> {
let mut values = Vec::new();
let name = expect_type!(self.next()?, Symbol)?;
values.push(name);
match opcode {
Opcode::Db => {
// db can take string literals or u8 immediates
while !self.tokens.is_empty() {
let token = self
.tokens
.last()
.expect("Expected a token for data definition, but found none");
match token {
Token::StringLit(_) => {
values.push(self.tokens.pop().expect(
"Expected a token for data definition, but found none",
));
}
Token::Immediate(val) if u8::try_from(*val).is_ok() => {
values.push(self.tokens.pop().expect(
"Expected a token for data definition, but found none",
));
}
_ => break,
}
}
}
Opcode::Dh => {
// dh can take u16 immediates
while !self.tokens.is_empty() {
let token = self
.tokens
.last()
.expect("Expected a token for data definition, but found none");
match token {
Token::StringLit(_) => {
values.push(self.tokens.pop().expect(
"Expected a token for data definition, but found none",
));
}
Token::Immediate(val) if u16::try_from(*val).is_ok() => {
values.push(self.tokens.pop().expect(
"Expected a token for data definition, but found none",
));
}
_ => break,
}
}
}
Opcode::Dw => {
// dw can take u32 immediates
while !self.tokens.is_empty() {
match self
.tokens
.last()
.expect("Expected a token for data definition, but found none")
{
Token::StringLit(_) => {
values.push(self.tokens.pop().expect(
"Expected a token for data definition, but found none",
));
}
Token::Immediate(val) => {
values.push(self.tokens.pop().expect(
"Expected a token for data definition, but found none",
));
}
_ => break,
}
}
}
_ => unreachable!(),
}
Ok(values)
}
fn next(&mut self) -> Result<Token, AssembleError> {
if self.tokens.is_empty() {
Err(AssembleError::UnexpectedEof)
} else {
Ok(self
.tokens
.pop()
.expect("tokens vector was unexpectedly empty in next()"))
}
}
fn peek_next(&self) -> Result<Token, AssembleError> {
if self.tokens.is_empty() {
Err(AssembleError::UnexpectedEof)
} else {
Ok(self
.tokens
.last()
.expect("peek_next called on empty tokens vector")
.clone())
}
}
}
-156
View File
@@ -1,156 +0,0 @@
use std::{
collections::HashMap,
fs::canonicalize,
path::{Path, PathBuf},
};
use common::prelude::Register;
use crate::assembler::quick_hash;
use crate::assembler::{
log,
model::{Module, Node, Opcode, Symbol, Token},
};
use crate::{assembler::AssembleError, node};
pub fn resolve_symbols(nodes: &mut [Node]) -> Result<(), AssembleError> {
let symbol_table = generate_symbol_table(nodes);
for node in nodes.iter_mut() {
match node.opcode() {
Opcode::Jmp
| Opcode::Jeq
| Opcode::Jne
| Opcode::Jgt
| Opcode::Jge
| Opcode::Jlt
| Opcode::Jle
| Opcode::Lli
| Opcode::Lui => {
if let Token::Symbol(symbol) = node
.arg(0)
.expect("Expected argument 0 for jump-like opcode")
{
if let Some(address) = symbol_table.get(&symbol) {
node.tokens[0] = Token::Immediate(*address);
} else {
return Err(AssembleError::UndefinedSymbol(symbol));
}
}
}
_ => (),
}
}
Ok(())
}
fn generate_symbol_table(nodes: &[Node]) -> HashMap<Symbol, u32> {
let mut table = HashMap::new();
for (i, node) in nodes.iter().enumerate() {
if let Some(symbol) = node.label() {
table.insert(symbol, 4 * i as u32);
}
}
table
}
pub fn resolve_dependencies(
mut nodes: Vec<Node>,
base_dir: &Path,
) -> Result<Vec<Node>, AssembleError> {
// First we get a list of imports.
let mut dependencies = Vec::new();
for node in &nodes {
if node.opcode() == Opcode::Include {
// we want the path, and the name
let name = if let Token::Symbol(name) = node
.arg(0)
.expect("Expected argument #0 for Include directive.")
{
name.name.clone()
} else {
unreachable!()
}; //node.2.get(0).unwrap()
let Ok(Token::StringLit(path)) = node.arg(1) else {
unreachable!()
};
let full_path = base_dir.join(path);
let canonical_path = full_path
.canonicalize()
.map_err(|_| AssembleError::InvalidFile(full_path.clone()))?;
let hash = quick_hash(&canonical_path);
dependencies.push((name, hash));
}
}
let mut changes = Vec::<(u32, u32, Symbol)>::new();
// now we resolve the symbols on all the nodes
// we need to check all operands for unresolved signals
for (i, node) in nodes.clone().iter().enumerate() {
let Node {
tokens: operands, ..
} = node;
for (j, token) in operands.iter().enumerate() {
if let Token::Symbol(symbol) = token {
for d in &dependencies {
if let Module::Unresolved(name) = symbol.module.clone() {
if name != d.0 {
continue;
}
let symbol = Symbol {
name: symbol.name.clone(),
module: Module::Resolved(d.1),
};
changes.push((i as u32, j as u32, symbol));
}
}
}
}
}
for (i, j, symbol) in changes {
nodes[i as usize].tokens[j as usize] = Token::Symbol(symbol);
}
Ok(nodes)
}
pub fn create_sections(nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
let mut res = Vec::<Node>::with_capacity(nodes.len());
res.push(node!(None, Opcode::Segment, Token::Immediate(0)));
for n in nodes.iter() {
if n.opcode() == Opcode::Data {
res.push(n.clone());
}
}
let start = res.len() + 1;
res.insert(
0,
node!(
None,
Opcode::Jmp,
Token::Immediate(start as u32 * 4),
Token::Register(Register::Zero)
),
);
for n in nodes.iter() {
if !matches!(n.opcode(), Opcode::Data | Opcode::Include) {
res.push(n.clone());
}
}
*nodes = res;
Ok(())
}
+374
View File
@@ -0,0 +1,374 @@
//! Simple compiler engine that orchestrates the entire compilation process.
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::path::Path;
use std::sync::mpsc;
use std::thread;
use crate::error::{AssembleErrorKind, IoErrorKind};
use crate::{
context::AssemblerContext,
error::AssembleError,
model::module::ModuleId,
source::{token::Token, tokeniser::Tokeniser},
};
use common::instructions::Instruction;
/// Error type for the `CompilerEngine`
#[derive(Debug)]
pub enum EngineError {
/// Assembly error during compilation
Assembly(AssembleError),
/// Channel communication error
Channel(String),
/// Other generic error
Other(String),
}
impl fmt::Display for EngineError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Assembly(e) => write!(f, "Assembly error: {e}"),
Self::Channel(msg) => write!(f, "Channel error: {msg}"),
Self::Other(msg) => write!(f, "Engine error: {msg}"),
}
}
}
impl std::error::Error for EngineError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Assembly(e) => Some(e),
Self::Channel(_) | Self::Other(_) => None,
}
}
}
// Convert from AssembleError
impl From<AssembleError> for EngineError {
fn from(error: AssembleError) -> Self {
Self::Assembly(error)
}
}
// Convert from mpsc::SendError
impl<T> From<mpsc::SendError<T>> for EngineError {
fn from(error: mpsc::SendError<T>) -> Self {
Self::Channel(format!("Send error: {error}"))
}
}
// Convert from mpsc::RecvError
impl From<mpsc::RecvError> for EngineError {
fn from(error: mpsc::RecvError) -> Self {
Self::Channel(format!("Receive error: {error}"))
}
}
// Convert from mpsc::TryRecvError
impl From<mpsc::TryRecvError> for EngineError {
fn from(error: mpsc::TryRecvError) -> Self {
Self::Channel(format!("Try receive error: {error}"))
}
}
// Convert from String for generic errors
impl From<String> for EngineError {
fn from(error: String) -> Self {
Self::Other(error)
}
}
// Convert from &str for convenience
impl From<&str> for EngineError {
fn from(error: &str) -> Self {
Self::Other(error.to_string())
}
}
/// Simple compiler engine that orchestrates the entire compilation process.
pub struct CompilerEngine {
result_tx: mpsc::Sender<Result<Vec<Instruction>, EngineError>>,
result_rx: Option<mpsc::Receiver<Result<Vec<Instruction>, EngineError>>>,
is_running: bool,
}
impl CompilerEngine {
/// Create a new compiler engine
#[must_use]
pub fn new() -> Self {
let (tx, rx) = mpsc::channel();
Self {
result_tx: tx,
result_rx: Some(rx),
is_running: false,
}
}
/// Start the compilation process in a separate thread
pub fn start_compilation<P: AsRef<Path>>(&mut self, src: P) {
if self.is_running {
return;
}
let src = src.as_ref().to_path_buf();
let tx = self.result_tx.clone();
thread::spawn(move || {
let result = assemble(&src).map_err(EngineError::from);
let _ = tx.send(result); // Ignore send errors if receiver is dropped
});
self.is_running = true;
}
/// Check if compilation is complete and get the result
pub fn try_get_result(&mut self) -> Option<Result<Vec<Instruction>, EngineError>> {
if !self.is_running {
return None;
}
match self
.result_rx
.as_ref()
.expect("result_rx should be Some while compilation is running")
.try_recv()
{
Ok(result) => {
self.is_running = false;
Some(result)
}
Err(mpsc::TryRecvError::Empty) => None,
Err(mpsc::TryRecvError::Disconnected) => {
self.is_running = false;
Some(Err(EngineError::Channel(
"Compilation thread disconnected".to_string(),
)))
}
}
}
/// Block until compilation is complete and return the result
pub fn wait_for_result(&mut self) -> Result<Vec<Instruction>, EngineError> {
if !self.is_running {
return Err(EngineError::Other("No compilation in progress".to_string()));
}
let result = self
.result_rx
.take()
.expect("result_rx should be Some while waiting for compilation result")
.recv()
.map_err(EngineError::from)?;
self.is_running = false;
result
}
/// Add a source file to be compiled (for compatibility with old interface)
pub fn add_source_file<P: AsRef<Path>>(
&mut self,
path: P,
) -> Result<(), EngineError> {
let path = path.as_ref().to_path_buf();
// Verify file exists
if !path.exists() {
return Err(EngineError::Assembly(AssembleError::new_other_error(
AssembleErrorKind::Io(crate::error::IoError::new(
IoErrorKind::NotFound,
Some(format!("Source file not found: {}", path.display())),
)),
)));
}
// For now, just validate the file exists
// TODO: Could store multiple files for batch compilation
Ok(())
}
/// Compile all added source files (synchronous version)
pub fn compile(&mut self) -> Result<CompileResult, EngineError> {
// This is a placeholder that matches the old interface
// For now, return empty result since we don't have a specific file to compile
Ok(CompileResult {
modules: Vec::new(),
tokens: HashMap::new(),
})
}
/// Get access to the assembler context (placeholder)
pub fn context(&self) -> Result<&AssemblerContext, EngineError> {
// For now, return an error since we're using the threaded approach
// TODO: Integrate context properly when we have more compilation phases
Err(EngineError::Other(
"Context not available in threaded mode".to_string(),
))
}
}
impl Default for CompilerEngine {
fn default() -> Self {
Self::new()
}
}
/// Main assembly function that orchestrates the compilation process
fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
// Verify the file exists
if !src.exists() {
return Err(AssembleError::new_other_error(AssembleErrorKind::Io(
crate::error::IoError::new(
IoErrorKind::NotFound,
Some(format!("Source file not found: {}", src.display())),
),
)));
}
let mut modules = HashSet::new();
let mut all_tokens = HashMap::new();
let mut module_ids = Vec::new();
// Create a new assembler context for this compilation
let context = AssemblerContext::new();
// Process the main file and its dependencies
prepare_dependency(
src,
&mut modules,
&mut all_tokens,
&mut module_ids,
&context,
)?;
// Phase 2: Parse tokens into AST (placeholder for now)
// TODO: Add parser here when implemented
println!("Phase 2: Parsing {} modules...", module_ids.len());
// Phase 3: Symbol resolution (placeholder for now)
// TODO: Add symbol resolution here when implemented
println!("Phase 3: Resolving symbols...");
// Phase 4: Code generation (placeholder for now)
// TODO: Add code generation here when implemented
println!("Phase 4: Generating code...");
// For now, return empty instructions since we don't have the full pipeline yet
Ok(Vec::new())
}
/// Prepare a dependency (file) for compilation
fn prepare_dependency(
path: &Path,
modules: &mut HashSet<u64>,
all_tokens: &mut HashMap<ModuleId, Vec<Token>>,
module_ids: &mut Vec<ModuleId>,
context: &AssemblerContext,
) -> Result<(), AssembleError> {
let filename = path.file_name().and_then(|n| n.to_str()).ok_or_else(|| {
AssembleError::new_other_error(AssembleErrorKind::Io(crate::error::IoError::new(
IoErrorKind::InvalidData,
Some("Failed to get file name from path".to_string()),
)))
})?;
// Calculate a simple hash for the file (similar to quick_hash)
let file_hash = calculate_file_hash(path);
// Skip if we've already processed this module
if modules.contains(&file_hash) {
return Ok(());
}
modules.insert(file_hash);
if let Ok(canonical_path) = path.canonicalize() {
println!("Building {} [{}]", filename, canonical_path.display());
}
// Phase 1: Tokenize the file
println!("Tokenising {filename}");
let tokeniser = Tokeniser::new(path, context)?;
let tokens = tokeniser.tokenise()?;
// Get the module ID that was registered during tokenization
let module_id = get_module_id_for_file(path, context)?;
all_tokens.insert(module_id, tokens);
module_ids.push(module_id);
// TODO: Parse tokens to find dependencies (.include directives, etc.)
// For now, we'll just process the single file
println!("Resolving dependencies for {filename}");
Ok(())
}
/// Calculate a simple hash for a file path (similar to the old `quick_hash`)
fn calculate_file_hash(path: &Path) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
if let Ok(canonical) = path.canonicalize() {
canonical.hash(&mut hasher);
} else {
path.hash(&mut hasher);
}
hasher.finish()
}
/// Get the module ID for a given source file
fn get_module_id_for_file(
file_path: &Path,
context: &AssemblerContext,
) -> Result<ModuleId, AssembleError> {
{
let registry = context.module_registry.read()?;
// Find module by path.
for module in registry.modules() {
if module.path == file_path {
return Ok(module.id);
}
}
}
Err(AssembleError::new_other_error(AssembleErrorKind::Io(
crate::error::IoError::new(
IoErrorKind::NotFound,
Some(format!(
"Module not found for file: {}",
file_path.display()
)),
),
)))
}
/// Result of compilation. This is useless at present but compiles.
#[derive(Debug)]
pub struct CompileResult {
pub modules: Vec<ModuleId>,
pub tokens: HashMap<ModuleId, Vec<Token>>,
}
impl CompileResult {
/// Get tokens for a specific module
#[must_use]
pub fn get_tokens(&self, module_id: &ModuleId) -> Option<&Vec<Token>> {
self.tokens.get(module_id)
}
/// Get all module IDs
#[must_use]
pub fn module_ids(&self) -> &[ModuleId] {
&self.modules
}
/// Get total number of tokens across all modules
#[must_use]
pub fn total_tokens(&self) -> usize {
self.tokens.values().map(std::vec::Vec::len).sum()
}
}
+28
View File
@@ -0,0 +1,28 @@
//! This module contains the global asembler context to be passed to functions that need
//! it.
use std::sync::RwLock;
use crate::{model::module_registry::ModuleRegistry, symtab::SymbolTable};
/// Global state to be passed around.
pub struct AssemblerContext {
pub symbol_table: RwLock<SymbolTable>,
pub module_registry: RwLock<ModuleRegistry>,
}
impl Default for AssemblerContext {
fn default() -> Self {
Self::new()
}
}
impl AssemblerContext {
#[must_use]
pub fn new() -> Self {
Self {
symbol_table: RwLock::new(SymbolTable::new()),
module_registry: RwLock::new(ModuleRegistry::new()),
}
}
}
+275
View File
@@ -0,0 +1,275 @@
//! This module contains code for various types of errors that may occur when assembling a
//! set of source DSA files.
use std::fmt::{Debug, Display};
use crate::source::{source_info::SourceInfo, tokeniser::error::TokeniserError};
/// An error that may occur during the assembly of a set of source files.
#[derive(Debug)]
pub struct AssembleError {
/// Display implementation can handle when the source code information is shown or
/// not.
source_info: Option<SourceInfo>,
/// The type of assembly error that occurred.
kind: AssembleErrorKind,
/// Whether context should be added to errors being printed. This might get changed
/// to Verbosity in the future.
display_quietly: bool,
}
impl AssembleError {
#[must_use]
pub const fn new_source_error(
source_info: SourceInfo,
kind: AssembleErrorKind,
) -> Self {
Self {
source_info: Some(source_info),
kind,
display_quietly: false,
}
}
#[must_use]
pub const fn new_other_error(kind: AssembleErrorKind) -> Self {
Self {
source_info: None,
kind,
display_quietly: true,
}
}
/// Prints a parser error to the screen.
fn print_parser_error(
&self,
f: &mut std::fmt::Formatter<'_>,
parse_error: &ParserError,
) -> std::fmt::Result {
let Some(source_info) = &self.source_info else {
write!(
f,
"parser error thrown with no source information. Error: {parse_error}"
)?;
return Ok(());
};
writeln!(f, "parser error of type `{parse_error}`.\n")?;
// Prints out the context for our error.
if !self.display_quietly {
source_info.print_context_with_underline().map_err(|e| {
_ = writeln!(f, "print context error: {e}");
std::fmt::Error {}
})?;
}
Ok(())
}
/// Prints a tokeniser error to the screen.
fn print_tokeniser_error(
&self,
f: &mut std::fmt::Formatter<'_>,
err: &TokeniserError,
) -> std::fmt::Result {
let Some(source_info) = &self.source_info else {
write!(
f,
"Tokeniser error thrown with no source information. Error: {err}"
)?;
return Ok(());
};
writeln!(f, "tokeniser error of type `{err}`.\n")?;
// Prints out the context for our error.
source_info.print_context_with_underline().map_err(|e| {
_ = writeln!(f, "Print context error: {e}");
std::fmt::Error {}
})?;
Ok(())
}
}
impl Display for AssembleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(info) = &self.source_info {
write!(f, "At {info}, got ")?;
match &self.kind {
AssembleErrorKind::Parser(err) => self.print_parser_error(f, err)?,
AssembleErrorKind::Tokeniser(err) => {
self.print_tokeniser_error(f, err)?;
}
_ => write!(f, "{}", self.kind)?,
}
writeln!(f)?;
return Ok(());
}
// Handle errors without SourceInfo.
write!(f, "{}", self.kind)?;
Ok(())
}
}
/// Marker trait.
impl std::error::Error for AssembleError {}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum AssembleErrorKind {
/// Usually unexpected I/O errors. Not normally recoverable.
Io(IoError),
/// Errors emitted from the [`Tokeniser`].
Tokeniser(TokeniserError),
Parser(ParserError),
Symbol(SymbolError),
Codegen(CodegenError),
Threading(ThreadingError),
/// Returned for code where the functionality has not yet been implemented but we
/// don't want the program to panic.
Unimplemented(&'static str),
}
#[derive(Debug, Clone)]
pub enum ParserError {
UnexpectedToken,
MissingOperand,
InvalidInstruction,
MissingLabel,
DuplicateLabel,
}
impl Display for ParserError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnexpectedToken => write!(f, "unexpected token"),
Self::MissingOperand => write!(f, "missing operand"),
Self::InvalidInstruction => write!(f, "invalid instruction"),
Self::MissingLabel => write!(f, "missing label"),
Self::DuplicateLabel => write!(f, "duplicate label"),
}
}
}
#[derive(Debug, Clone)]
pub enum SymbolError {
Undefined,
Duplicate,
CircularDependency,
InvalidReference,
}
impl Display for SymbolError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Undefined => write!(f, "undefined symbol"),
Self::Duplicate => write!(f, "duplicate symbol"),
Self::CircularDependency => write!(f, "circular dependency"),
Self::InvalidReference => write!(f, "invalid reference"),
}
}
}
#[derive(Debug, Clone)]
pub enum CodegenError {
InvalidOperand,
OutOfRange,
UnsupportedInstruction,
}
impl Display for CodegenError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidOperand => write!(f, "invalid operand"),
Self::OutOfRange => write!(f, "out of range"),
Self::UnsupportedInstruction => write!(f, "unsupported instruction"),
}
}
}
#[derive(Debug, Clone)]
pub enum ThreadingError {
LockFailed,
ThreadPanic,
}
impl Display for ThreadingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::LockFailed => write!(f, "lock failed"),
Self::ThreadPanic => write!(f, "thread panic"),
}
}
}
#[derive(Debug, Clone)]
pub struct IoError {
msg: Option<String>,
kind: IoErrorKind,
}
impl IoError {
#[must_use]
pub const fn new(kind: IoErrorKind, msg: Option<String>) -> Self {
Self { msg, kind }
}
}
#[derive(Debug, Clone)]
pub enum IoErrorKind {
NotFound,
PermissionDenied,
InvalidData,
Other,
}
impl std::fmt::Display for IoErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotFound => write!(f, "file not found"),
Self::PermissionDenied => write!(f, "permission denied"),
Self::InvalidData => write!(f, "invalid data"),
Self::Other => write!(f, "other I/O error"),
}
}
}
impl std::fmt::Display for IoError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.kind)?;
if let Some(msg) = &self.msg {
write!(f, ", \"{msg}\"")?;
}
Ok(())
}
}
impl Display for AssembleErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Tokeniser(why) => write!(f, "tokeniser error: {why}"),
Self::Unimplemented(why) => write!(f, "used unimplemented feature: {why}"),
Self::Io(why) => write!(f, "problem occurred with I/O: {why}"),
#[allow(unreachable_patterns)]
_ => write!(
f,
"unhandled error type in Display implementation! See error.rs!"
),
}
}
}
pub mod conversions;
+67
View File
@@ -0,0 +1,67 @@
use std::{
io::ErrorKind,
sync::{PoisonError, RwLockReadGuard, RwLockWriteGuard},
};
use crate::error::{AssembleError, IoError, IoErrorKind};
use super::{AssembleErrorKind, ThreadingError};
impl From<std::io::Error> for IoError {
fn from(err: std::io::Error) -> Self {
let kind = match err.kind() {
ErrorKind::NotFound => IoErrorKind::NotFound,
ErrorKind::PermissionDenied => IoErrorKind::PermissionDenied,
ErrorKind::InvalidData => IoErrorKind::InvalidData,
_ => IoErrorKind::Other,
};
let msg = err.to_string();
Self::new(kind, Some(msg))
}
}
impl From<std::io::Error> for AssembleError {
fn from(err: std::io::Error) -> Self {
Self::new_other_error(AssembleErrorKind::Io(err.into()))
}
}
// TODO: Maybe attempt recovery? To be honest we don't want any threads to panic at all,
// or we want them all to panic spectacularly.
impl<T> From<PoisonError<RwLockReadGuard<'_, T>>> for AssembleError {
fn from(err: PoisonError<RwLockReadGuard<'_, T>>) -> Self {
Self::new_other_error(AssembleErrorKind::Threading(err.into()))
}
}
impl<T> From<PoisonError<RwLockReadGuard<'_, T>>> for ThreadingError {
fn from(_err: PoisonError<RwLockReadGuard<'_, T>>) -> Self {
Self::LockFailed
}
}
impl<T> From<PoisonError<RwLockWriteGuard<'_, T>>> for AssembleError {
fn from(err: PoisonError<RwLockWriteGuard<'_, T>>) -> Self {
Self::new_other_error(AssembleErrorKind::Threading(err.into()))
}
}
impl<T> From<PoisonError<RwLockWriteGuard<'_, T>>> for ThreadingError {
fn from(_err: PoisonError<RwLockWriteGuard<'_, T>>) -> Self {
Self::LockFailed
}
}
impl From<std::fmt::Error> for AssembleError {
fn from(err: std::fmt::Error) -> Self {
IoError::new(IoErrorKind::Other, Some(err.to_string())).into()
}
}
impl From<IoError> for AssembleError {
fn from(err: IoError) -> Self {
Self::new_other_error(AssembleErrorKind::Io(err))
}
}
+13 -7
View File
@@ -12,12 +12,18 @@
clippy::match_wildcard_for_single_variants
)]
pub mod assembler;
pub mod brainf;
pub mod tooling;
pub mod args;
// pub mod tooling;
pub mod compiler_engine;
pub mod context;
pub mod error;
pub mod model;
pub mod source;
pub mod symtab;
mod util;
pub mod prelude {
pub use crate::assembler::CompilerEngine;
pub use crate::tooling::project;
}
// pub mod prelude {}
use num_cpus as _;
use threadpool as _;
+79 -44
View File
@@ -1,57 +1,92 @@
use assembler::{brainf, prelude::*};
use std::{fs, io::Write, path::PathBuf};
use std::sync::Arc;
fn main() {
// Parse command line arguments
let args: Vec<String> = std::env::args().collect();
use assembler::{
error::{AssembleError, AssembleErrorKind, ParserError},
model::module::Module,
source::{source_info::SourceInfo, token::TokenType, tokeniser::Tokeniser},
};
use common as _;
use num_cpus as _;
use threadpool as _;
if args.len() == 2 && args[1] == "init" {
project::tool_libcreate();
std::process::exit(0);
// use clap::Parser;
// use std::{fs, io::Write, path::PathBuf};
fn main() -> Result<(), AssembleError> {
// // Parse command line arguments
// let args: Vec<String> = std::env::args().collect();
let contents = include_bytes!("../../resources/dsa/bf.dsa").to_vec();
let module = Arc::new(Module::new("resources/dsa/bf.dsa")?);
let tok = Tokeniser::from_data(contents, module.clone());
let ts = tok
.tokenise()?
.into_iter()
.filter(|t| !matches!(t.token_type, TokenType::Eof | TokenType::Newline));
for t in ts {
t.source_info.print_context_with_underline()?;
}
if args.len() == 2 && args[1] == "brainf" {
let src = PathBuf::from("brainf.bf");
let result = brainf::build(&src);
let test_error: AssembleError = AssembleError::new_source_error(
SourceInfo::new(45, module.clone(), 4..7),
AssembleErrorKind::Parser(ParserError::InvalidInstruction),
);
let mut file = match fs::File::create("brainf.dsb") {
Err(e) => {
eprintln!("Failed to create output file: {}", e);
std::process::exit(1);
}
Ok(file) => file,
};
eprintln!("\n\n{test_error}");
for instruction in result {
if let Err(e) = file.write(&instruction.encode().to_be_bytes()) {
eprintln!("Failed to write to output file: {}", e);
std::process::exit(1);
}
}
Ok(())
std::process::exit(0);
}
// let _clap_args = assembler::args::Args::parse();
if args.len() != 5 || args[1] != "-i" || args[3] != "-o" {
eprintln!("Usage: {} -i input_path -o output_path", args[0]);
std::process::exit(1);
}
// if args.len() == 2 && args[1] == "init" {
// // project::tool_libcreate();
// std::process::exit(0);
// }
let input_path = &args[2];
let output_path = &args[4];
let src = PathBuf::from(input_path);
// if args.len() == 2 && args[1] == "brainf" {
// let src = PathBuf::from("brainf.bf");
// // let result = brainf::build(&src);
// Initialize the compiler engine
let mut compiler = CompilerEngine::new();
compiler.start_compilation(&src);
// let mut file = match fs::File::create("brainf.dsb") {
// Err(e) => {
// eprintln!("Failed to create output file: {e}");
// std::process::exit(1);
// }
// Ok(file) => file,
// };
// Or block until done
let result = compiler.wait_for_result().unwrap();
// // for instruction in result {
// // if let Err(e) = file.write(&instruction.encode().to_be_bytes()) {
// // eprintln!("Failed to write to output file: {e}");
// // std::process::exit(1);
// // }
// // }
for instruction in result {
if let Err(e) = fs::write(output_path, instruction.encode().to_be_bytes()) {
eprintln!("Failed to write to output file: {}", e);
std::process::exit(1);
}
}
// std::process::exit(0);
// }
// if args.len() != 5 || args[1] != "-i" || args[3] != "-o" {
// eprintln!("Usage: {} -i input_path -o output_path", args[0]);
// std::process::exit(1);
// }
// let input_path = &args[2];
// let output_path = &args[4];
// let src = PathBuf::from(input_path);
// // Initialize the compiler engine
// let mut compiler = CompilerEngine::new();
// compiler.start_compilation(&src);
// // Or block until done
// let result = compiler.wait_for_result().unwrap();
// for instruction in result {
// if let Err(e) = fs::write(output_path, instruction.encode().to_be_bytes()) {
// eprintln!("Failed to write to output file: {e}");
// std::process::exit(1);
// }
// }
}
+5
View File
@@ -0,0 +1,5 @@
//! This module contains the underlying data models and enums used by the Assembler.
pub mod module;
pub mod module_registry;
pub mod symbol;
+110
View File
@@ -0,0 +1,110 @@
//! This module contains the [`Module`] type and associated types. Each compilation unit
//! (file) is represented by a module which is used to namespace "function" calls and
//! accesses to global variables.
//!
//! They have unique identifiers in the form of UUIDs.
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use regex::Regex;
use uuid::Uuid;
use crate::{
error::{AssembleError, AssembleErrorKind, IoError, IoErrorKind},
model::module_registry::ModuleRegistry,
};
/// The ID for a module. A tuple struct for type safety.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
pub struct ModuleId(Uuid);
impl ModuleId {
#[must_use]
pub fn new() -> Self {
Self(Uuid::new_v4())
}
#[must_use]
pub const fn from_module(module: &Module) -> Self {
module.id
}
/// Convenience method to get the [`Module`] from a [`ModuleId`].
#[must_use]
pub fn to_module<'m>(&self, registry: &'m ModuleRegistry) -> Option<&'m Arc<Module>> {
registry.get(self)
}
/// Convenience method to get the [`Module`] name from a [`ModuleId`].
#[must_use]
pub fn to_module_name(self, registry: &ModuleRegistry) -> Option<&str> {
self.to_module(registry).map(|module| module.name.as_str())
}
}
impl Default for ModuleId {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for ModuleId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
/// A single source file or compilation unit. Stores its own symbol table.
#[derive(Debug, Clone)]
pub struct Module {
/// The name of the module. This is typically the name of the file, less the `.dsa`
/// extension.
pub name: String,
/// The file path to the module. This is an absolute path.
pub path: PathBuf,
/// A unique ID for this module.
pub id: ModuleId,
}
impl std::hash::Hash for Module {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.0.hash(state);
}
}
impl Module {
pub fn new<P: AsRef<Path>>(p: P) -> Result<Self, AssembleError> {
let path = p.as_ref().to_path_buf();
let name = Self::extract_module_name(&path)?;
let id = ModuleId::new();
Ok(Self { name, path, id })
}
/// Gets the name for a module from the path.
fn extract_module_name<P: AsRef<Path>>(path: P) -> Result<String, AssembleError> {
let extensions_regex = Regex::new(".(dsa|S|asm)$")
.expect("For some reason the regular expression failed to compile!");
let module_name = path
.as_ref()
.file_name()
.map(|f| f.to_string_lossy())
.ok_or_else(|| {
AssembleError::new_other_error(AssembleErrorKind::Io(IoError::new(
IoErrorKind::InvalidData,
Some(
"the filename couldn't be extracted, is it valid UTF-8?"
.to_string(),
),
)))
})?;
// Strip any file extensions given. We don't care for now.
let out = extensions_regex.replace(&module_name, "");
Ok(out.to_string())
}
}
+44
View File
@@ -0,0 +1,44 @@
//! This module contains the code for the module registry. This is a singleton storing all
//! the modules being assembled.
use std::{collections::HashMap, sync::Arc};
use super::module::{Module, ModuleId};
/// Stores all the [`Module`]'s to be assembled.
pub struct ModuleRegistry {
modules: HashMap<ModuleId, Arc<Module>>,
}
impl Default for ModuleRegistry {
fn default() -> Self {
Self::new()
}
}
impl ModuleRegistry {
#[must_use]
pub fn new() -> Self {
Self {
modules: HashMap::new(),
}
}
/// Gets a [`Module`] by ID.
#[must_use]
pub fn get(&self, module_id: &ModuleId) -> Option<&Arc<Module>> {
self.modules.get(module_id)
}
/// Adds a [`Module`] and returns its [`ModuleId`].
pub fn add(&mut self, module: Arc<Module>) -> ModuleId {
let id = module.id;
self.modules.insert(id, module);
id
}
/// Returns an iterator of modules.
pub fn modules(&self) -> impl Iterator<Item = &Arc<Module>> {
self.modules.values()
}
}
+165
View File
@@ -0,0 +1,165 @@
//! This module contains the definitions for a Symbol.
use std::collections::HashSet;
use uuid::Uuid;
use crate::{model::module::ModuleId, symtab::SymbolTable};
/// Tuple struct for type safety. Has methods for fetching symbols by ID.
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub struct SymbolId(Uuid);
impl From<Symbol> for SymbolId {
fn from(sym: Symbol) -> Self {
sym.id
}
}
impl Default for SymbolId {
fn default() -> Self {
Self::new()
}
}
impl SymbolId {
#[must_use]
pub fn new() -> Self {
Self(Uuid::new_v4())
}
/// Convenience method to get the [`Module`] from a [`ModuleId`].
#[must_use]
pub fn to_module<'s>(&self, registry: &'s SymbolTable) -> Option<&'s Symbol> {
registry.get(self)
}
/// Convenience method to get the [`Module`] name from a [`ModuleId`].
#[must_use]
pub fn to_module_name(self, registry: &SymbolTable) -> Option<&str> {
self.to_module(registry).map(|module| module.name.as_str())
}
}
/// A symbol is a named reference that may be resolved later to an address by a linker.
#[derive(Debug)]
pub struct Symbol {
/// Stored cheaply instead of the name. Shall be stored in the symbol table under
/// this key.
pub id: SymbolId,
/// The human-readable name for the symbol.
pub name: String,
pub visibility: Visibility,
pub symbol_type: SymbolType,
/// The id of the module the symbol is defined in. This will be different for symbols
/// in different objects.
pub module_id: ModuleId,
/// Whether or not the symbol requires relocating.
pub needs_relocation: bool,
/// A list of the symbol's dependencies.
///
/// e.g.
///
/// ```dsa
/// main:
/// call another_func
///
/// another_func:
/// // Code goes here
/// ret
/// ```
///
/// Where `main` depends on `another_func`.
pub dependencies: HashSet<SymbolId>,
/// The address of the symbol.
pub address: Option<u32>,
/// The section the symbol is in.
/// TODO: Perhaps make this a proper type?
pub section: Option<String>,
pub size: Option<u32>,
}
impl Symbol {
#[must_use]
pub fn new(
name: String,
module_id: ModuleId,
visibility: Visibility,
symbol_type: SymbolType,
) -> Self {
Self {
id: SymbolId::new(),
name,
module_id,
address: None,
section: None,
size: None,
visibility,
symbol_type,
needs_relocation: false,
dependencies: HashSet::new(),
}
}
/// Adds a dependency on another [`Symbol`].
pub fn add_dependency(&mut self, dep: SymbolId) {
if self.id == dep {
return;
}
// We can resolve a lot of addresses at assembly time, but not really foreign
// ones, since we aren't certain of their position.
//
/* TODO: Handle this for flat binary case i.e. no linker required. This may be
* done using a similar method to before, such as just concatenating all
* of the files together and handling jumps and halts.
*
* > Ask Harry or read the initial code.
*/
if self.dependencies.insert(dep) {
self.needs_relocation = true;
}
}
/// Returns whether a [`Symbol`] depends on `symbol_id`.
#[must_use]
pub fn depends_on(&self, symbol_id: &SymbolId) -> bool {
self.dependencies.contains(symbol_id)
}
/// Removes a [`Symbol`] from the dependency set.
pub fn remove_dependency(&mut self, symbol_id: &SymbolId) {
self.dependencies.remove(symbol_id);
if self.dependencies.is_empty() {
self.needs_relocation = false;
}
}
}
#[derive(Debug, Copy, Clone)]
/// The visibility of the symbol in different object files.
pub enum Visibility {
/// `STB_PUBLIC` under the ELF spec. Visible in all other object files. Shall be used
/// for labels. Remember labels are namespaced in different files so they won't clash
/// with one another.
Public,
/// Only visible within this object file. `STB_LOCAL` under ELF spec. Shall be used
/// for data definitions unless they are marked public.
Local,
/// `STB_WEAK` under the ELF spec. Potentially unused.
Weak,
}
#[derive(Debug)]
pub enum SymbolType {
LabelOrFunction,
Variable,
}
+29
View File
@@ -0,0 +1,29 @@
//! This module contains anything within the first stage of assembly, i.e. the
//! tokenisation stage, or utility functions for reading input files.
use std::{
io::{BufRead, Lines},
path::Path,
};
use crate::error::AssembleError;
pub mod lines;
pub mod opcode;
pub mod source_info;
pub mod token;
pub mod token_info;
pub mod tokeniser;
/// Attempts to load and open a source file, returning a [`Vec<u8>`] or an
/// [`AssembleError`].
pub fn load_source_bytes<P: AsRef<Path>>(p: P) -> Result<Vec<u8>, AssembleError> {
let path = p.as_ref();
Ok(std::fs::read(path)?)
}
/// Get the lines from a [`BufReader`].
pub fn reader_lines<R: BufRead>(rdr: R) -> Lines<R> {
rdr.lines()
}
+76
View File
@@ -0,0 +1,76 @@
//! Enhanced lines iterator that tracks line numbers and character positions.
use std::io::{BufRead, BufReader, Cursor};
use crate::error::AssembleError;
/// Iterator that yields lines with their line numbers and character spans.
pub struct LinesWithSpans<R: BufRead> {
reader: R,
line_number: usize,
total_chars: usize,
buffer: String,
}
#[derive(Debug, Clone)]
pub struct LineSpan {
/// The line number.
pub line_number: usize,
/// The contents of the line.
pub content: String,
/// Character offset from start of file.
pub start_char: usize,
/// End character offset (exclusive).
pub end_char: usize,
}
impl<R: BufRead> LinesWithSpans<R> {
pub const fn new(reader: R) -> Self {
Self {
reader,
line_number: 0,
total_chars: 0,
buffer: String::new(),
}
}
}
impl<R: BufRead> Iterator for LinesWithSpans<R> {
type Item = Result<LineSpan, AssembleError>;
fn next(&mut self) -> Option<Self::Item> {
self.buffer.clear();
match self.reader.read_line(&mut self.buffer) {
Ok(0) => None, // EOF
Ok(bytes_read) => {
self.line_number += 1;
let start_char = self.total_chars;
self.total_chars += bytes_read;
// Remove trailing newline for cleaner processing
let content = if self.buffer.ends_with('\n') {
self.buffer[..self.buffer.len() - 1].to_string()
} else {
self.buffer.clone()
};
Some(Ok(LineSpan {
line_number: self.line_number,
content,
start_char,
end_char: self.total_chars,
}))
}
Err(e) => Some(Err(e.into())),
}
}
}
/// Helper function to create lines iterator from data.
#[must_use]
pub fn lines_with_spans(data: &[u8]) -> LinesWithSpans<BufReader<Cursor<&[u8]>>> {
let cursor = Cursor::new(data);
let reader = BufReader::new(cursor);
LinesWithSpans::new(reader)
}
@@ -1,77 +1,285 @@
//! This module contains instructions for tokenisation.
use std::{fmt, str::FromStr};
use common::prelude::Register;
use common::prelude::{ITypeArgs, Instruction, Interrupt, RTypeArgs};
use crate::assembler::AssembleError;
use crate::{
error::{AssembleError, AssembleErrorKind},
source::source_info::SourceInfo,
};
#[derive(Debug, Clone)]
pub struct Node {
pub symbol: Option<Symbol>,
pub opcode: Opcode,
pub tokens: Vec<Token>,
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Opcode {
Nop,
Mov,
Movs,
Ldb,
Ldbs,
Ldh,
Ldhs,
Ldw,
Stb,
Sth,
Stw,
Lli,
Lui,
Jmp,
Jeq,
Jne,
Jgt,
Jge,
Jlt,
Jle,
Cmp,
Inc,
Dec,
Shl,
Shr,
Add,
Sub,
And,
Or,
Not,
Xor,
Nand,
Nor,
Xnor,
Int,
Irt,
Hlt,
AddI,
SubI,
// Pseudo-instructions
Db,
Dh,
Dw,
Resb,
Resh,
Resw,
Push,
Pop,
Pusha,
Popa,
Lwi,
Call,
Return,
// Meta instructions (these aren't present in the binary as instructions)
Include,
Data,
Segment,
}
impl Node {
#[must_use]
pub const fn new(symbol: Option<Symbol>, opcode: Opcode, tokens: Vec<Token>) -> Self {
Self {
symbol,
opcode,
tokens,
#[derive(Debug)]
pub enum OpcodeFromStrError {
InvalidRegister(&'static str),
InvalidOpcode(String),
}
impl std::fmt::Display for OpcodeFromStrError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidRegister(reg) => write!(f, "register does not exist: {reg}"),
Self::InvalidOpcode(op) => write!(f, "instruction does not exist: {op}"),
}
}
}
impl std::error::Error for OpcodeFromStrError {}
impl Opcode {
pub const OPCODES: &[&str] = &[
// Real instructions (0x00-0x26)
"nop", "mov", "movs", "ldb", "ldbs", "ldh", "ldhs", "ldw", "stb", "sth", "stw",
"lli", "lui", "jmp", "jeq", "jne", "jgt", "jge", "jlt", "jle", "cmp", "inc",
"dec", "shl", "shr", "add", "sub", "and", "or", "not", "xor", "nand", "nor",
"xnor", "int", "irt", "hlt", "addi", "subi", // Pseudo-instructions
"db", "dh", "dw", "resb", "resh", "resw", "push", "pop", "lwi", "call", "return",
"pusha", "popa", // meta instructions
"include",
];
pub fn to_instruction(
&self,
source_info: SourceInfo,
) -> Result<Instruction, AssembleError> {
match self {
Self::Nop => Ok(Instruction::Nop),
Self::Mov => Ok(Instruction::Mov(RTypeArgs::default())),
Self::Movs => Ok(Instruction::MovSigned(RTypeArgs::default())),
Self::Ldb => Ok(Instruction::LoadByte(ITypeArgs::default())),
Self::Ldbs => Ok(Instruction::LoadByteSigned(ITypeArgs::default())),
Self::Ldh => Ok(Instruction::LoadHalfword(ITypeArgs::default())),
Self::Ldhs => Ok(Instruction::LoadHalfwordSigned(ITypeArgs::default())),
Self::Ldw => Ok(Instruction::LoadWord(ITypeArgs::default())),
Self::Stb => Ok(Instruction::StoreByte(ITypeArgs::default())),
Self::Sth => Ok(Instruction::StoreHalfword(ITypeArgs::default())),
Self::Stw => Ok(Instruction::StoreWord(ITypeArgs::default())),
Self::Lli => Ok(Instruction::LoadLowerImmediate(ITypeArgs::default())),
Self::Lui => Ok(Instruction::LoadUpperImmediate(ITypeArgs::default())),
Self::Jmp => Ok(Instruction::Jump(ITypeArgs::default())),
Self::Jeq => Ok(Instruction::JumpEq(ITypeArgs::default())),
Self::Jne => Ok(Instruction::JumpNeq(ITypeArgs::default())),
Self::Jgt => Ok(Instruction::JumpGt(ITypeArgs::default())),
Self::Jge => Ok(Instruction::JumpGe(ITypeArgs::default())),
Self::Jlt => Ok(Instruction::JumpLt(ITypeArgs::default())),
Self::Jle => Ok(Instruction::JumpLe(ITypeArgs::default())),
Self::Cmp => Ok(Instruction::Compare(RTypeArgs::default())),
Self::Inc => Ok(Instruction::Increment(RTypeArgs::default())),
Self::Dec => Ok(Instruction::Decrement(RTypeArgs::default())),
Self::Shl => Ok(Instruction::ShiftLeft(RTypeArgs::default())),
Self::Shr => Ok(Instruction::ShiftRight(RTypeArgs::default())),
Self::Add => Ok(Instruction::Add(RTypeArgs::default())),
Self::Sub => Ok(Instruction::Sub(RTypeArgs::default())),
Self::And => Ok(Instruction::And(RTypeArgs::default())),
Self::Or => Ok(Instruction::Or(RTypeArgs::default())),
Self::Not => Ok(Instruction::Not(RTypeArgs::default())),
Self::Xor => Ok(Instruction::Xor(RTypeArgs::default())),
Self::Nand => Ok(Instruction::Nand(RTypeArgs::default())),
Self::Nor => Ok(Instruction::Nor(RTypeArgs::default())),
Self::Xnor => Ok(Instruction::Xnor(RTypeArgs::default())),
Self::Int => Ok(Instruction::Interrupt(Interrupt::default())),
Self::Irt => Ok(Instruction::IntReturn),
Self::Hlt => Ok(Instruction::Halt),
Self::AddI => Ok(Instruction::AddImmediate(ITypeArgs::default())),
Self::SubI => Ok(Instruction::SubImmediate(ITypeArgs::default())),
Self::Segment => Ok(Instruction::Segment(0)),
_ => Err(AssembleError::new_source_error(
source_info,
AssembleErrorKind::Unimplemented(
"Opcode::to_instruction called on an instruction that does not exist in common.",
),
)),
}
}
#[must_use]
pub fn label(&self) -> Option<Symbol> {
self.symbol.clone()
pub const fn to_opcode_value(&self) -> Option<u8> {
match self {
Self::Nop => Some(0x00),
Self::Mov => Some(0x01),
Self::Movs => Some(0x02),
Self::Ldb => Some(0x03),
Self::Ldbs => Some(0x04),
Self::Ldh => Some(0x05),
Self::Ldhs => Some(0x06),
Self::Ldw => Some(0x07),
Self::Stb => Some(0x08),
Self::Sth => Some(0x09),
Self::Stw => Some(0x0A),
Self::Lli => Some(0x0B),
Self::Lui => Some(0x0C),
Self::Jmp => Some(0x0D),
Self::Jeq => Some(0x0E),
Self::Jne => Some(0x0F),
Self::Jgt => Some(0x10),
Self::Jge => Some(0x11),
Self::Jlt => Some(0x12),
Self::Jle => Some(0x13),
Self::Cmp => Some(0x14),
Self::Inc => Some(0x15),
Self::Dec => Some(0x16),
Self::Shl => Some(0x17),
Self::Shr => Some(0x18),
Self::Add => Some(0x19),
Self::Sub => Some(0x1A),
Self::And => Some(0x1B),
Self::Or => Some(0x1C),
Self::Not => Some(0x1D),
Self::Xor => Some(0x1E),
Self::Nand => Some(0x1F),
Self::Nor => Some(0x20),
Self::Xnor => Some(0x21),
Self::Int => Some(0x22),
Self::Irt => Some(0x23),
Self::Hlt => Some(0x24),
Self::AddI => Some(0x25),
Self::SubI => Some(0x26),
// TODO: Maybe recombine pseudos?
Self::Segment => Some(0x27),
// Pseudo-instructions don't have opcode values
_ => None,
}
}
#[must_use]
pub const fn opcode(&self) -> Opcode {
self.opcode
}
#[must_use]
pub fn args(&self) -> Vec<Token> {
self.tokens.clone()
}
pub fn arg(&self, index: usize) -> Result<Token, AssembleError> {
self.args()
.get(index)
.cloned()
.ok_or(AssembleError::InvalidArg)
}
}
impl fmt::Display for Node {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let symbol = self
.label()
.as_ref()
.map_or_else(String::new, |symbol| format!("{symbol}:\n"));
write!(
f,
"\x1b[93m{} \t\x1b[94m{} \x1b[37m{:?} \x1b[0m",
symbol,
self.opcode(),
self.args()
pub const fn is_pseudo_instruction(&self) -> bool {
matches!(
self,
Self::Db
| Self::Dh
| Self::Dw
| Self::Resb
| Self::Resh
| Self::Resw
| Self::Push
| Self::Pop
| Self::Lwi
)
}
}
impl fmt::Display for Symbol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} ( module: {})", self.name, self.module)
}
}
impl FromStr for Opcode {
type Err = OpcodeFromStrError;
impl fmt::Display for Module {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Unresolved(name) => write!(f, "{name}"),
Self::Resolved(name) => write!(f, "{name}"),
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"nop" => Ok(Self::Nop),
"mov" => Ok(Self::Mov),
"movs" => Ok(Self::Movs),
"ldb" => Ok(Self::Ldb),
"ldbs" => Ok(Self::Ldbs),
"ldh" => Ok(Self::Ldh),
"ldhs" => Ok(Self::Ldhs),
"ldw" => Ok(Self::Ldw),
"stb" => Ok(Self::Stb),
"sth" => Ok(Self::Sth),
"stw" => Ok(Self::Stw),
"lli" => Ok(Self::Lli),
"lui" => Ok(Self::Lui),
"jmp" => Ok(Self::Jmp),
"jeq" => Ok(Self::Jeq),
"jne" => Ok(Self::Jne),
"jgt" => Ok(Self::Jgt),
"jge" => Ok(Self::Jge),
"jlt" => Ok(Self::Jlt),
"jle" => Ok(Self::Jle),
"cmp" => Ok(Self::Cmp),
"inc" => Ok(Self::Inc),
"dec" => Ok(Self::Dec),
"shl" => Ok(Self::Shl),
"shr" => Ok(Self::Shr),
"add" => Ok(Self::Add),
"sub" => Ok(Self::Sub),
"and" => Ok(Self::And),
"or" => Ok(Self::Or),
"not" => Ok(Self::Not),
"xor" => Ok(Self::Xor),
"nand" => Ok(Self::Nand),
"nor" => Ok(Self::Nor),
"xnor" => Ok(Self::Xnor),
"int" => Ok(Self::Int),
"irt" => Ok(Self::Irt),
"hlt" => Ok(Self::Hlt),
"addi" => Ok(Self::AddI),
"subi" => Ok(Self::SubI),
"db" => Ok(Self::Db),
"dh" => Ok(Self::Dh),
"dw" => Ok(Self::Dw),
"resb" => Ok(Self::Resb),
"resh" => Ok(Self::Resh),
"resw" => Ok(Self::Resw),
"push" => Ok(Self::Push),
"pop" => Ok(Self::Pop),
"lwi" => Ok(Self::Lwi),
"include" => Ok(Self::Include),
"call" => Ok(Self::Call),
"return" => Ok(Self::Return),
"pusha" => Ok(Self::Pusha),
"popa" => Ok(Self::Popa),
_ => Err(OpcodeFromStrError::InvalidOpcode(s.to_string())),
}
}
}
@@ -139,281 +347,3 @@ impl fmt::Display for Opcode {
}
}
}
#[derive(Debug, Clone, Eq)]
pub struct Symbol {
pub name: String,
pub module: Module,
}
impl std::hash::Hash for Symbol {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.module.hash(state);
}
}
impl PartialEq for Symbol {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.module == other.module
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Module {
Resolved(u64),
Unresolved(String),
}
#[derive(Debug, Clone)]
pub enum Token {
Symbol(Symbol),
Register(Register),
Immediate(u32),
StringLit(String),
Opcode(Opcode),
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum TokenType {
Symbol,
Register,
Immediate,
StringLit,
Opcode,
}
impl TokenType {
#[must_use]
pub const fn from_token(token: &Token) -> Self {
match token {
Token::Symbol(_) => Self::Symbol,
Token::Register(_) => Self::Register,
Token::Immediate(_) => Self::Immediate,
Token::StringLit(_) => Self::StringLit,
Token::Opcode(_) => Self::Opcode,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Opcode {
// Real instructions (0x00-0x26)
Nop,
Mov,
Movs,
Ldb,
Ldbs,
Ldh,
Ldhs,
Ldw,
Stb,
Sth,
Stw,
Lli,
Lui,
Jmp,
Jeq,
Jne,
Jgt,
Jge,
Jlt,
Jle,
Cmp,
Inc,
Dec,
Shl,
Shr,
Add,
Sub,
And,
Or,
Not,
Xor,
Nand,
Nor,
Xnor,
Int,
Irt,
Hlt,
AddI,
SubI,
// Pseudo-instructions
Db,
Dh,
Dw,
Resb,
Resh,
Resw,
Push,
Pop,
Pusha,
Popa,
Lwi,
Call,
Return,
// meta instructions (these aren't present in the binary as instructions)
Include,
Data,
Segment,
}
#[derive(Debug)]
pub enum OpcodeFromStrError {
InvalidRegister(&'static str),
InvalidOpcode(String),
}
impl std::fmt::Display for OpcodeFromStrError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidRegister(reg) => write!(f, "register does not exist: {reg}"),
Self::InvalidOpcode(op) => write!(f, "instruction does not exist: {op}"),
}
}
}
impl std::error::Error for OpcodeFromStrError {}
impl FromStr for Opcode {
type Err = OpcodeFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"nop" => Ok(Self::Nop),
"mov" => Ok(Self::Mov),
"movs" => Ok(Self::Movs),
"ldb" => Ok(Self::Ldb),
"ldbs" => Ok(Self::Ldbs),
"ldh" => Ok(Self::Ldh),
"ldhs" => Ok(Self::Ldhs),
"ldw" => Ok(Self::Ldw),
"stb" => Ok(Self::Stb),
"sth" => Ok(Self::Sth),
"stw" => Ok(Self::Stw),
"lli" => Ok(Self::Lli),
"lui" => Ok(Self::Lui),
"jmp" => Ok(Self::Jmp),
"jeq" => Ok(Self::Jeq),
"jne" => Ok(Self::Jne),
"jgt" => Ok(Self::Jgt),
"jge" => Ok(Self::Jge),
"jlt" => Ok(Self::Jlt),
"jle" => Ok(Self::Jle),
"cmp" => Ok(Self::Cmp),
"inc" => Ok(Self::Inc),
"dec" => Ok(Self::Dec),
"shl" => Ok(Self::Shl),
"shr" => Ok(Self::Shr),
"add" => Ok(Self::Add),
"sub" => Ok(Self::Sub),
"and" => Ok(Self::And),
"or" => Ok(Self::Or),
"not" => Ok(Self::Not),
"xor" => Ok(Self::Xor),
"nand" => Ok(Self::Nand),
"nor" => Ok(Self::Nor),
"xnor" => Ok(Self::Xnor),
"int" => Ok(Self::Int),
"irt" => Ok(Self::Irt),
"hlt" => Ok(Self::Hlt),
"addi" => Ok(Self::AddI),
"subi" => Ok(Self::SubI),
"db" => Ok(Self::Db),
"dh" => Ok(Self::Dh),
"dw" => Ok(Self::Dw),
"resb" => Ok(Self::Resb),
"resh" => Ok(Self::Resh),
"resw" => Ok(Self::Resw),
"push" => Ok(Self::Push),
"pop" => Ok(Self::Pop),
"lwi" => Ok(Self::Lwi),
"include" => Ok(Self::Include),
"call" => Ok(Self::Call),
"return" => Ok(Self::Return),
"pusha" => Ok(Self::Pusha),
"popa" => Ok(Self::Popa),
_ => Err(OpcodeFromStrError::InvalidOpcode(s.to_string())),
}
}
}
impl Opcode {
pub const OPCODES: &[&str] = &[
// Real instructions (0x00-0x26)
"nop", "mov", "movs", "ldb", "ldbs", "ldh", "ldhs", "ldw", "stb", "sth", "stw",
"lli", "lui", "jmp", "jeq", "jne", "jgt", "jge", "jlt", "jle", "cmp", "inc",
"dec", "shl", "shr", "add", "sub", "and", "or", "not", "xor", "nand", "nor",
"xnor", "int", "irt", "hlt", "addi", "subi", // Pseudo-instructions
"db", "dh", "dw", "resb", "resh", "resw", "push", "pop", "lwi", "call", "return",
"pusha", "popa", // meta instructions
"include",
];
#[must_use]
pub const fn to_opcode_value(&self) -> Option<u8> {
match self {
Self::Nop => Some(0x00),
Self::Mov => Some(0x01),
Self::Movs => Some(0x02),
Self::Ldb => Some(0x03),
Self::Ldbs => Some(0x04),
Self::Ldh => Some(0x05),
Self::Ldhs => Some(0x06),
Self::Ldw => Some(0x07),
Self::Stb => Some(0x08),
Self::Sth => Some(0x09),
Self::Stw => Some(0x0A),
Self::Lli => Some(0x0B),
Self::Lui => Some(0x0C),
Self::Jmp => Some(0x0D),
Self::Jeq => Some(0x0E),
Self::Jne => Some(0x0F),
Self::Jgt => Some(0x10),
Self::Jge => Some(0x11),
Self::Jlt => Some(0x12),
Self::Jle => Some(0x13),
Self::Cmp => Some(0x14),
Self::Inc => Some(0x15),
Self::Dec => Some(0x16),
Self::Shl => Some(0x17),
Self::Shr => Some(0x18),
Self::Add => Some(0x19),
Self::Sub => Some(0x1A),
Self::And => Some(0x1B),
Self::Or => Some(0x1C),
Self::Not => Some(0x1D),
Self::Xor => Some(0x1E),
Self::Nand => Some(0x1F),
Self::Nor => Some(0x20),
Self::Xnor => Some(0x21),
Self::Int => Some(0x22),
Self::Irt => Some(0x23),
Self::Hlt => Some(0x24),
Self::AddI => Some(0x25),
Self::SubI => Some(0x26),
Self::Segment => Some(0x27),
// Pseudo-instructions don't have opcode values
_ => None,
}
}
#[must_use]
pub const fn is_pseudo_instruction(&self) -> bool {
matches!(
self,
Self::Db
| Self::Dh
| Self::Dw
| Self::Resb
| Self::Resh
| Self::Resw
| Self::Push
| Self::Pop
| Self::Lwi
)
}
}
+4
View File
@@ -0,0 +1,4 @@
//! This module contains code for handling pseudo opcodes.
/// Pseudo instructions that cannot simply be lowered to ISA instructions.
pub enum PseudoOpcode {}
+104
View File
@@ -0,0 +1,104 @@
//! This file contains information on where a [`Token`] or [`Node`] is within the source
//! code for more informative errors.
//!
//! This will likely be attached to a [`Token`] which will in turn be attached to an AST
//! [`Node`].
use std::{
fmt::{Display, Write},
fs::File,
io::BufReader,
sync::Arc,
};
use crate::{
error::{AssembleError, AssembleErrorKind, IoError, IoErrorKind},
model::module::Module,
source::lines::LinesWithSpans,
};
/// Information on where the token is within the source.
#[derive(Debug, Clone)]
pub struct SourceInfo {
/// The line number within the source file underpinned by `module_id`.
pub line_number: usize,
/// The [`Module`] the source code is associated with.
pub module: Arc<Module>,
/// The indexes where this token may be found (line-local).
pub span: std::ops::Range<usize>,
}
impl Display for SourceInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}:{}",
self.module.path.display(),
self.line_number,
self.span.start + 1
)
}
}
impl SourceInfo {
#[must_use]
pub const fn new(
line_no: usize,
module: Arc<Module>,
span: std::ops::Range<usize>,
) -> Self {
Self {
line_number: line_no,
module,
span,
}
}
/// Prints out where in the source code the error originated with an underline similar
/// to what rustc does.
pub fn print_context_with_underline(&self) -> Result<(), AssembleError> {
let f = File::open(&self.module.path)?;
let rdr = BufReader::new(f);
let mut lines = LinesWithSpans::new(rdr);
let Some(line_result) = lines.nth(self.line_number - 1) else {
// Handle a line not existing.
return Err(AssembleError::new_source_error(
self.clone(),
AssembleErrorKind::Io(IoError::new(
IoErrorKind::Other,
Some(format!(
"the line {} does not exist in input file `{}` but source info suggested otherwise!.",
self.line_number,
self.module.path.display()
)),
)),
));
};
let line_span = line_result?;
// Print the line number and line content.
println!("{:>4} | {}", self.line_number, line_span.content);
let mut pad_left = String::new();
write!(pad_left, "{:>4} ", "")?;
let mut underline = String::new();
for _ in 0..self.span.start {
pad_left.push(' ');
}
for _ in self.span.start..self.span.end.min(line_span.content.len()) {
underline.push('^');
}
// Print the underline in red and bold.
// TODO: Use a crate to make this extra portable.
println!("{pad_left}\x1b[1;31m{underline}\x1b[0m");
Ok(())
}
}
+91
View File
@@ -0,0 +1,91 @@
//! Contains [`TokenType`] and [`Token`]'s. Adapted from Harry's old lexer since it was
//! easier to build from scratch and edit his code than it would be to try and wrangle it
//! into shape.
use common::prelude::*;
use crate::source::{
opcode::Opcode,
source_info::SourceInfo,
token_info::{DirectiveToken, LabelToken, RegisterToken, SymbolToken},
};
/// Represents the different types of tokens that can be produced by the tokeniser.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TokenType {
/// Symbol reference (e.g., `loop_start`, `my_data`).
Symbol(SymbolToken),
/// CPU register (e.g., `r1`, `r2`, `sp`).
Register(RegisterToken),
/// Immediate value (e.g., `42`, `0xFF`).
Immediate(u32),
/// String literal (e.g., `"hello world"`).
String(String),
/// Intermediate token for multiline strings (filtered out in final output)
StringContinuation,
/// Assembly instruction (e.g., `add`, `jmp`, `nop`).
Instruction(Opcode),
/// Label definition (e.g., `loop_start:`).
Label(LabelToken),
/// Assembler directive (e.g., `.global`, `.section`, `.dw`).
Directive(DirectiveToken),
/// Comment (e.g., `// this is a comment`).
Comment,
/// Comma separator.
Comma,
/// End of line.
Newline,
/// End of file.
Eof,
}
#[derive(Debug)]
pub struct Token {
/// The type of the token.
pub token_type: TokenType,
/// Where in the source code is this [`Token`]?
pub source_info: SourceInfo,
}
impl Token {
#[must_use]
pub const fn new(token_type: TokenType, source_info: SourceInfo) -> Self {
Self {
token_type,
source_info,
}
}
#[must_use]
pub const fn symbol(name: String, source_info: SourceInfo) -> Self {
Self::new(TokenType::Symbol(SymbolToken { name }), source_info)
}
#[must_use]
pub const fn label(name: String, source_info: SourceInfo) -> Self {
Self::new(TokenType::Label(LabelToken { name }), source_info)
}
#[must_use]
pub const fn instruction(op: Opcode, source_info: SourceInfo) -> Self {
Self::new(TokenType::Instruction(op), source_info)
}
#[must_use]
pub const fn register(reg: Register, source_info: SourceInfo) -> Self {
Self::new(TokenType::Register(RegisterToken { reg }), source_info)
}
#[must_use]
pub const fn immediate(value: u32, source_info: SourceInfo) -> Self {
Self::new(TokenType::Immediate(value), source_info)
}
#[must_use]
pub const fn directive(directive: String, source_info: SourceInfo) -> Self {
Self::new(
TokenType::Directive(DirectiveToken { directive }),
source_info,
)
}
}
+34
View File
@@ -0,0 +1,34 @@
use common::prelude::Register;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SymbolToken {
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct LabelToken {
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DirectiveToken {
pub directive: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RegisterToken {
pub reg: Register,
}
impl RegisterToken {
#[must_use]
pub const fn new(reg: Register) -> Self {
Self { reg }
}
/// Returns the name of a valid [`Register`]
#[must_use]
pub fn name(&self) -> String {
self.reg.to_string()
}
}
+421
View File
@@ -0,0 +1,421 @@
//! This file contains the [`Tokeniser`], which consumes a [`Vec`] of input bytes and
//! outputs a [`Vec<Token>`].
use std::{path::Path, str::FromStr, sync::Arc};
use regex::Regex;
use common::prelude::*;
use crate::{
context::AssemblerContext,
error::{AssembleError, AssembleErrorKind},
model::module::Module,
source::{
lines::{LineSpan, lines_with_spans},
load_source_bytes,
opcode::Opcode,
source_info::SourceInfo,
token::{Token, TokenType},
token_info::{DirectiveToken, LabelToken, RegisterToken, SymbolToken},
tokeniser::error::TokeniserError,
},
};
pub mod error;
#[cfg(test)]
mod tests;
/// Consumes a [`Vec<u8>`] and outputs a [`Vec`] of [Token]'s.
pub struct Tokeniser {
/// The data in the file.
pub data: Vec<u8>,
/// A copy of the Module in which the file is situated.
pub module: Arc<Module>,
// Pre-compiled regex patterns
label_regex: Regex,
register_regex: Regex,
immediate_regex: Regex,
directive_regex: Regex,
instruction_regex: Regex,
symbol_regex: Regex,
comment_regex: Regex,
// String parsing state
in_string: bool,
string_buffer: String,
string_start_line: usize,
string_start_column: usize,
}
impl Tokeniser {
#[must_use]
pub fn from_data(data: Vec<u8>, module: Arc<Module>) -> Self {
Self {
data,
module,
label_regex: Regex::new(r"^([a-zA-Z_][a-zA-Z0-9_]*):")
.expect("Failed to compile label regex pattern"),
register_regex: Regex::new(
r"^(rg[0-9a-f]+|acc|spr|bpr|ret|idr|mmr|zero|noreg|pcx)\b",
)
.expect("Failed to compile register regex pattern"),
immediate_regex: Regex::new(
r"^(0x[0-9a-fA-F_]+|0b[0-1_]+|0o[0-7_]+|[0-9_]+)",
)
.expect("Failed to compile immediate regex pattern"),
directive_regex: Regex::new(r"^(res[bwh]|d[bwh]|include|section|global|local)\b")
.expect("Failed to compile directive regex pattern"),
instruction_regex: Regex::new(
r"^(nop|movs?|ld[bhw]s?|st[bhw]|l[lu]i|j(mp|[egl][qte])|cmp|[id]nc|sh[lr]|add[i]?|sub[i]?|x?n?or|and|not|i[rd]t|hlt|lhwmm|lidt|push[a]?|pop[a]?|lwi|return|call)\b",
)
.expect("Failed to compile instruction regex pattern"),
symbol_regex: Regex::new(r"^([a-zA-Z_][a-zA-Z0-9_]*)::{2}([a-zA-Z0-9_]*)|([a-zA-Z_][a-zA-Z0-9_]*)")
.expect("Failed to compile symbol regex pattern"),
comment_regex: Regex::new("^//.*")
.expect("Failed to compile comment regex pattern"),
// Initialize string parsing state
in_string: false,
string_buffer: String::new(),
string_start_line: 0,
string_start_column: 0,
}
}
/// Creates a [`Tokeniser`] from a file path. Also creates the underlying [`Module`]
/// for you.
pub fn new<P: AsRef<Path>>(
path: P,
ctx: &AssemblerContext,
) -> Result<Self, AssembleError> {
let path = path.as_ref().to_path_buf();
let data = load_source_bytes(&path)?;
let module = Arc::new(Module::new(path)?);
{
let mut module_registry = ctx.module_registry.write()?;
module_registry.add(module.clone());
}
Ok(Self::from_data(data, module))
}
// Note that modules are tokenised in their own threads, possibly in parallel.
pub fn tokenise(mut self) -> Result<Vec<Token>, AssembleError> {
let mut token_stream = Vec::new();
let data = self.data.clone();
let lines = lines_with_spans(&data);
// Process each line
for line_result in lines {
let line_span = line_result?;
let trimmed = line_span.content.trim();
// Skip empty lines and add newline tokens
if trimmed.is_empty() {
token_stream.push(Token::new(
TokenType::Newline,
SourceInfo::new(line_span.line_number, self.module.clone(), 0..1),
));
continue;
}
// Actually tokenise the line content
let line_tokens = self.tokenise_line(&line_span)?;
token_stream.extend(line_tokens);
// Add newline token at end of line
token_stream.push(Token::new(
TokenType::Newline,
SourceInfo::new(
line_span.line_number,
self.module.clone(),
line_span.content.len()..line_span.content.len(),
),
));
}
// Add EOF token
token_stream.push(Token::new(
TokenType::Eof,
SourceInfo::new(0, self.module.clone(), 0..0),
));
Ok(token_stream)
}
fn tokenise_line(
&mut self,
line_span: &LineSpan,
) -> Result<Vec<Token>, AssembleError> {
let mut tokens = Vec::new();
let mut remaining = line_span.content.as_str();
let mut column = 0;
// Skip leading whitespace
let trimmed_start = remaining.trim_start();
column += remaining.len() - trimmed_start.len();
remaining = trimmed_start;
while !remaining.is_empty() {
let start_column = column;
// Try to match a token.
let (token_type, consumed) =
self.match_token(remaining, line_span.line_number, column)?;
// Filter out string continuation tokens and comments.
match token_type {
TokenType::StringContinuation => {
// Don't add to token stream, just consume input
}
TokenType::Comment => {
// Don't add to token stream, consume rest of line
break;
}
_ => {
tokens.push(Token::new(
token_type,
SourceInfo::new(
line_span.line_number,
self.module.clone(),
start_column..start_column + consumed,
),
));
}
}
// Advance position.
remaining = &remaining[consumed..];
column += consumed;
// Skip whitespace.
let before_trim = remaining.len();
remaining = remaining.trim_start();
column += before_trim - remaining.len();
}
Ok(tokens)
}
fn try_match_comment(&self, input: &str) -> Option<(TokenType, usize)> {
let caps = self.comment_regex.captures(input)?;
let len = caps.get(0)?.len();
Some((TokenType::Comment, len))
}
fn try_match_label(&self, input: &str) -> Option<(TokenType, usize)> {
let caps = self.label_regex.captures(input)?;
let name = caps.get(1)?.as_str().to_string();
let len = caps.get(0)?.len();
Some((TokenType::Label(LabelToken { name }), len))
}
fn try_match_register(&self, input: &str) -> Option<(TokenType, usize)> {
let caps = self.register_regex.captures(input)?;
let captured_group = caps.get(1)?.as_str();
let len = caps.get(0)?.len();
let reg = Register::try_from(captured_group).ok()?;
Some((TokenType::Register(RegisterToken { reg }), len))
}
fn try_match_immediate(&self, input: &str) -> Option<(TokenType, usize)> {
let caps = self.immediate_regex.captures(input)?;
let value_str = caps.get(1)?.as_str();
let len = caps.get(0)?.len();
// Remove any underscores that were inserted for readability.
let value_str = value_str.replace('_', "");
let value = if let Some(hex_part) = value_str.strip_prefix("0x") {
u32::from_str_radix(hex_part, 16).ok()?
} else if let Some(bin_part) = value_str.strip_prefix("0b") {
u32::from_str_radix(bin_part, 2).ok()?
} else if let Some(oct_part) = value_str.strip_prefix("0o") {
u32::from_str_radix(oct_part, 8).ok()?
} else {
value_str.parse::<u32>().ok()?
};
Some((TokenType::Immediate(value), len))
}
fn try_match_directive(&self, input: &str) -> Option<(TokenType, usize)> {
let caps = self.directive_regex.captures(input)?;
let directive = caps.get(1)?.as_str().to_string();
let len = caps.get(0)?.len();
Some((TokenType::Directive(DirectiveToken { directive }), len))
}
fn try_match_instruction(&self, input: &str) -> Option<(TokenType, usize)> {
let caps = self.instruction_regex.captures(input)?;
let mnemonic = caps.get(1)?.as_str().to_string();
let len = caps.get(0)?.len();
let op = Opcode::from_str(&mnemonic).ok()?;
Some((TokenType::Instruction(op), len))
}
fn try_match_symbol(&self, input: &str) -> Option<(TokenType, usize)> {
let caps = self.symbol_regex.captures(input)?;
let len = caps.get(0)?.len();
// Check which capture group matched.
let name = if let Some(scoped_name) = caps.get(1) {
// Matched the scoped symbol pattern (name::scope).
format!("{}::{}", scoped_name.as_str(), caps.get(2)?.as_str())
} else if let Some(simple_name) = caps.get(3) {
simple_name.as_str().to_string()
} else {
return None;
};
Some((TokenType::Symbol(SymbolToken { name }), len))
}
fn try_match_string(
&mut self,
input: &str,
line_number: usize,
column: usize,
) -> Option<(TokenType, usize)> {
if self.in_string {
// We're continuing a multiline string
Some(self.handle_string_continuation(input, line_number, column))
} else {
// Look for the start of a new string
self.handle_string_start(input, line_number, column)
}
}
fn handle_string_start(
&mut self,
input: &str,
line_number: usize,
column: usize,
) -> Option<(TokenType, usize)> {
if !input.starts_with('"') {
return None;
}
// Find the closing quote on the same line
if let Some(end_pos) = input[1..].find('"') {
// Complete string on one line
let content = input[1..=end_pos].to_string();
let len = end_pos + 2; // +2 for both quotes
Some((TokenType::String(content), len))
} else {
// Start of multiline string
self.in_string = true;
self.string_start_line = line_number;
self.string_start_column = column;
self.string_buffer = input[1..].to_string(); // Everything after opening quote
self.string_buffer.push('\n'); // Add newline for multiline
// Consume the entire rest of the line
Some((TokenType::StringContinuation, input.len()))
}
}
fn handle_string_continuation(
&mut self,
input: &str,
_line_number: usize,
_column: usize,
) -> (TokenType, usize) {
// Look for closing quote
if let Some(end_pos) = input.find('"') {
// End of multiline string found
self.string_buffer.push_str(&input[..end_pos]);
self.in_string = false;
let content = std::mem::take(&mut self.string_buffer);
let len = end_pos + 1; // +1 for the closing quote
(TokenType::String(content), len)
} else {
// Continue multiline string
self.string_buffer.push_str(input);
self.string_buffer.push('\n'); // Add newline
// Consume the entire line
(TokenType::StringContinuation, input.len())
}
}
#[expect(clippy::range_plus_one, reason = "RangeInclusive is a different type!")]
fn match_token(
&mut self,
input: &str,
line_number: usize,
column: usize,
) -> Result<(TokenType, usize), AssembleError> {
if input.starts_with(',') {
return Ok((TokenType::Comma, 1));
}
// Check for string first (including multiline continuations).
if let Some(m) = self.try_match_string(input, line_number, column) {
return Ok(m);
}
if let Some(m) = self.try_match_directive(input) {
return Ok(m);
}
if let Some(m) = self.try_match_instruction(input) {
return Ok(m);
}
if let Some(m) = self.try_match_comment(input) {
return Ok(m);
}
if let Some(m) = self.try_match_label(input) {
return Ok(m);
}
if let Some(m) = self.try_match_register(input) {
return Ok(m);
}
if let Some(m) = self.try_match_immediate(input) {
return Ok(m);
}
if let Some(m) = self.try_match_symbol(input) {
return Ok(m);
}
let mut idx_iter = (column + 1)..;
let Some(idx) = idx_iter.next() else {
unreachable!()
};
let source = SourceInfo::new(line_number, self.module.clone(), idx..idx + 1);
// Handle miscellaneous characters.
if let Some(c) = input.chars().next() {
Err(AssembleError::new_source_error(
source,
AssembleErrorKind::Tokeniser(TokeniserError::UnexpectedChar(c)),
))
} else {
Err(AssembleError::new_source_error(
source,
AssembleErrorKind::Tokeniser(TokeniserError::UnexpectedEndOfInput(
input.len(),
)),
))
}
}
}
+41
View File
@@ -0,0 +1,41 @@
//! This module contains the error types for the tokeniser.
#[derive(Debug, Clone, Copy)]
/// Types of errors that may be returned during tokenisation.
pub enum TokeniserError {
/// An unexpected character was found in the source code.
UnexpectedChar(char),
/// An unterminated string literal was found. [`SourceInfo`] will be attached if this
/// was returned.
UnterminatedString,
/// An invalid number format was encountered when parsing a literal value
/// ([`TokenType::Immediate`]).
InvalidNumber(&'static str),
/// An unrecognized token was encountered.
UnrecognisedToken,
/// Returned if the consumed count was lower than the length of the input file.
/// This is a sign you will need to debug some [`Tokeniser`] code to ensure that
/// [`Tokeniser::match_token`] is working as intended.
///
/// First field is length of the line.
UnexpectedEndOfInput(usize),
}
impl TokeniserError {}
impl std::fmt::Display for TokeniserError {
#[rustfmt::skip]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnexpectedChar(c) => write!(f, "unexpected char '{c}' found in input")?,
Self::InvalidNumber(lit) => write!(f, "invalid integer literal \"{lit}\" found in input")?,
Self::UnrecognisedToken => write!(f, "unrecognised token found in input")?,
Self::UnterminatedString => write!(f, "unterminated string literal")?,
Self::UnexpectedEndOfInput(line_length) => write!(
f, "unexpected end of input, input length: {line_length}"
)?,
}
Ok(())
}
}
+418
View File
@@ -0,0 +1,418 @@
//! Unit tests for the tokenizer
use common::prelude::Register;
use crate::{
model::module::Module,
source::{
opcode::Opcode,
token::{Token, TokenType},
token_info::RegisterToken,
tokeniser::Tokeniser,
},
};
use std::{path::PathBuf, sync::Arc};
/// Helper function to create a tokenizer from source text
fn create_tokenizer_from_source(source: &str) -> Tokeniser {
let path = PathBuf::from("test.dsa");
let module = Module::new(path).expect("Cannot create module!");
Tokeniser::from_data(source.as_bytes().to_vec(), Arc::new(module))
}
/// Helper function to tokenize source and return tokens
fn tokenize_source(source: &str) -> Result<Vec<Token>, crate::error::AssembleError> {
let tokenizer = create_tokenizer_from_source(source);
tokenizer.tokenise()
}
/// Helper function to extract token types from a token vector
fn extract_token_types(tokens: &[Token]) -> Vec<&TokenType> {
tokens.iter().map(|t| &t.token_type).collect()
}
#[test]
fn test_empty_source() {
let tokens = tokenize_source("").expect("Failed to tokenize empty source");
// Should have at least EOF token
assert!(!tokens.is_empty());
assert!(matches!(
tokens
.last()
.expect("Expected at least one token")
.token_type,
TokenType::Eof
));
}
#[test]
fn test_whitespace_only() {
let tokens = tokenize_source(" \n \n ").expect("Failed to tokenize whitespace");
// Should have newlines and EOF
let token_types = extract_token_types(&tokens);
assert!(token_types.iter().any(|t| matches!(t, TokenType::Newline)));
assert!(token_types.iter().any(|t| matches!(t, TokenType::Eof)));
}
#[test]
fn test_single_instruction() {
let tokens = tokenize_source("add").expect("Failed to tokenize instruction");
let token_types = extract_token_types(&tokens);
// Should have instruction, newline, and EOF
assert!(
token_types
.iter()
.any(|t| matches!(t, TokenType::Instruction(_)))
);
if let TokenType::Instruction(instr) = &tokens[0].token_type {
assert_eq!(instr.to_string(), "add");
} else {
panic!("Expected instruction token");
}
}
#[test]
fn test_all_instructions() {
let instructions = ["add", "sub", "jmp", "call", "return", "lli", "nop", "hlt"];
for instr in &instructions {
let tokens = tokenize_source(instr).expect("Failed to tokenize instruction");
if let TokenType::Instruction(parsed_instr) = &tokens[0].token_type {
assert_eq!(parsed_instr.to_string(), *instr);
} else {
panic!("Expected instruction token for {instr}");
}
}
}
#[test]
fn test_registers() {
let test_cases = [("rg0", "rg0"), ("rgf", "rgf"), ("pcx", "pcx")];
for (input, expected) in &test_cases {
let tokens = tokenize_source(input).expect("Failed to tokenize register");
if let TokenType::Register(reg) = &tokens[0].token_type {
assert_eq!(reg.reg.to_string(), *expected);
} else {
panic!("Expected register token for {input}");
}
}
}
#[test]
fn test_immediates() {
let test_cases = [
("42", 42),
("0", 0),
("0xFF", 255),
("0x1234", 0x1234),
("0xDEADBEEF", 0xDEAD_BEEF),
("0o12", 0o12),
("0b101", 0b101),
];
for (input, expected) in &test_cases {
let tokens = tokenize_source(input).expect("Failed to tokenize immediate");
if let TokenType::Immediate(value) = &tokens[0].token_type {
assert_eq!(*value, *expected);
} else {
panic!("Expected immediate token for {input}");
}
}
}
#[test]
fn test_labels() {
let test_cases = [
("loop_start:", "loop_start"),
("main:", "main"),
("_private_label:", "_private_label"),
("Label123:", "Label123"),
];
for (input, expected) in &test_cases {
let tokens = tokenize_source(input).expect("Failed to tokenize label");
if let TokenType::Label(label) = &tokens[0].token_type {
assert_eq!(label.name, *expected);
} else {
panic!("Expected label token for {input}");
}
}
}
#[test]
fn test_directives() {
let test_cases = [
("global", "global"),
("section", "section"),
("local", "local"),
];
for (input, expected) in &test_cases {
let tokens = tokenize_source(input).expect("Failed to tokenize directive");
if let TokenType::Directive(directive) = &tokens[0].token_type {
assert_eq!(directive.directive, *expected);
} else {
panic!("Expected directive token for {input}");
}
}
}
#[test]
fn test_symbols() {
let test_cases = [
("my_symbol", "my_symbol"),
("_private", "_private"),
("Symbol123", "Symbol123"),
("camelCase", "camelCase"),
];
for (input, expected) in &test_cases {
let tokens = tokenize_source(input).expect("Failed to tokenize symbol");
if let TokenType::Symbol(symbol) = &tokens[0].token_type {
assert_eq!(symbol.name, *expected);
} else {
panic!("Expected symbol token for {input}");
}
}
}
#[test]
fn test_complex_instruction_line() {
let source = "addi rg1, rg2, 0xFF";
let tokens = tokenize_source(source).expect("Failed to tokenise complex instruction");
// Should have: instruction, register, comma, register, comma, immediate, newline, EOF
assert!(tokens.len() >= 6);
assert!(matches!(tokens[0].token_type, TokenType::Instruction(_)));
assert!(matches!(tokens[1].token_type, TokenType::Register(_)));
assert!(matches!(tokens[2].token_type, TokenType::Comma));
assert!(matches!(tokens[3].token_type, TokenType::Register(_)));
assert!(matches!(tokens[4].token_type, TokenType::Comma));
assert!(matches!(tokens[5].token_type, TokenType::Immediate(_)));
}
#[test]
fn test_multiline_with_comments() {
const EXPECTED_TOKEN_TYPES: [TokenType; 11] = [
TokenType::Instruction(Opcode::Add),
TokenType::Register(RegisterToken::new(Register::Rg0)),
TokenType::Comma,
TokenType::Register(RegisterToken::new(Register::Rg1)),
TokenType::Newline,
TokenType::Instruction(Opcode::SubI),
TokenType::Register(RegisterToken::new(Register::Rg2)),
TokenType::Comma,
TokenType::Immediate(10),
TokenType::Newline,
TokenType::Eof,
];
const SOURCE: &str = r"add rg0, rg1 // Another comment
subi rg2, 10";
let tokens =
tokenize_source(SOURCE).expect("Failed to tokenise source with comments");
let token_types = extract_token_types(&tokens);
assert_eq!(
token_types.len(),
EXPECTED_TOKEN_TYPES.len(),
"{token_types:#?}"
);
for (expected, got) in EXPECTED_TOKEN_TYPES.iter().zip(token_types.iter()) {
assert!(!(expected != *got), "Expected {expected:?}, got {got:?}");
}
}
#[test]
fn test_tokenise_brainf_interpreter() {
const SOURCE: &str = include_str!("../../../../resources/dsa/bf.dsa");
let tokens =
tokenize_source(SOURCE).expect("Failed to tokenise the brainfuck compiler!");
dbg!(tokens);
}
#[test]
fn test_string_literals() {
let test_cases = [
(r#""hello world""#, "hello world"),
(
r#""++++++++++++++++++++++++++++++++++++++++++++""#,
"++++++++++++++++++++++++++++++++++++++++++++",
),
(r#""Invalid Instruction!""#, "Invalid Instruction!"),
(r#""""#, ""),
];
for (input, expected) in &test_cases {
let tokens = tokenize_source(input).expect("Failed to tokenize string literal");
if let TokenType::String(value) = &tokens[0].token_type {
assert_eq!(value, expected);
} else {
panic!("Expected string token for {input}");
}
}
}
#[test]
fn test_data_directives() {
let test_cases = [("db", "db"), ("dw", "dw"), ("resb", "resb")];
for (input, expected) in &test_cases {
let tokens = tokenize_source(input).expect("Failed to tokenize data declaration");
if let TokenType::Directive(decl) = &tokens[0].token_type {
assert_eq!(decl.directive, *expected);
} else {
panic!("Expected data declaration token for {input}");
}
}
}
#[test]
fn test_include_directive() {
let source = r#"include print "./lib/print.dsa""#;
let tokens = tokenize_source(source).expect("Failed to tokenize include directive");
assert!(tokens.len() >= 3);
assert!(matches!(tokens[0].token_type, TokenType::Directive(_)));
assert!(matches!(tokens[1].token_type, TokenType::Symbol(_)));
assert!(matches!(tokens[2].token_type, TokenType::String(_)));
}
#[test]
fn test_hex_addresses() {
let test_cases = [("0x10000", 0x10000), ("0x30000", 0x30000)];
for (input, expected) in &test_cases {
let tokens = tokenize_source(input).expect("Failed to tokenize hex address");
if let TokenType::Immediate(value) = &tokens[0].token_type {
assert_eq!(*value, *expected);
} else {
panic!("Expected immediate token for {input}");
}
}
}
#[test]
fn test_memory_operations() {
let source = "ldw rg1, rg2";
let tokens = tokenize_source(source).expect("Failed to tokenize memory operation");
assert!(tokens.len() >= 4);
assert!(matches!(tokens[0].token_type, TokenType::Instruction(_)));
assert!(matches!(tokens[1].token_type, TokenType::Register(_)));
assert!(matches!(tokens[2].token_type, TokenType::Comma));
assert!(matches!(tokens[3].token_type, TokenType::Register(_)));
}
#[test]
fn test_function_calls() {
let source = "call print::print";
let tokens = tokenize_source(source).expect("Failed to tokenize function call");
assert!(tokens.len() >= 2);
assert!(matches!(tokens[0].token_type, TokenType::Instruction(_)));
// The symbol might be parsed differently depending on how :: is handled
// This test checks basic structure
assert!(
tokens
.iter()
.any(|t| matches!(t.token_type, TokenType::Symbol(_)))
);
}
#[test]
fn test_comments_are_ignored() {
let source = "add rg0, rg1 // this is a comment\nsub rg2, rg3";
let tokens = tokenize_source(source).expect("Failed to tokenize with comments");
// Comments should be stripped, so we should only have instruction tokens
let instruction_count = tokens
.iter()
.filter(|t| matches!(t.token_type, TokenType::Instruction(_)))
.count();
assert_eq!(instruction_count, 2);
}
#[test]
fn test_newline_always_present() {
// Test that even without explicit newline at end, one is added
let source = "add rg0, rg1"; // No newline at end
let tokens = tokenize_source(source).expect("Failed to tokenize without newline");
// Should have newline before EOF
let has_newline = tokens
.iter()
.any(|t| matches!(t.token_type, TokenType::Newline));
assert!(
has_newline,
"Expected newline to be added even when missing from input"
);
// EOF should be last.
assert!(matches!(
tokens
.last()
.expect("Expected at least one token")
.token_type,
TokenType::Eof
));
}
#[test]
fn test_complex_branching_code() {
let source = r"
cmp rg3, rg8
jeq increment
cmp rg3, rg9
jeq decrement";
let tokens = tokenize_source(source).expect("Failed to tokenize branching code");
let instruction_count = tokens
.iter()
.filter(|t| matches!(t.token_type, TokenType::Instruction(_)))
.count();
assert_eq!(instruction_count, 4);
let symbol_count = tokens
.iter()
.filter(|t| matches!(t.token_type, TokenType::Symbol(_)))
.count();
assert_eq!(symbol_count, 2); // increment and decrement labels
}
#[test]
fn test_stack_operations() {
let source = "push rg2\npop zero\npusha 2\npopa 2";
let tokens = tokenize_source(source).expect("Failed to tokenize stack operations");
let instruction_count = tokens
.iter()
.filter(|t| matches!(t.token_type, TokenType::Instruction(_)))
.count();
assert_eq!(instruction_count, 4);
}
+122
View File
@@ -0,0 +1,122 @@
//! This module contains the code for the Symbol Table, which can be written into object
//! files to support deferred relocations when using ELF files.
//!
//! It is also required for detection of duplicate symbols, and resolution in the flat
//! binary output type.
use crate::{
error::AssembleError,
model::{
module::ModuleId,
symbol::{Symbol, SymbolId, Visibility},
},
};
use std::collections::HashMap;
/// Global symbol table - single source of truth for all symbols.
/// Much simpler than per-module tables.
#[derive(Debug)]
pub struct SymbolTable {
/// All symbols by their ID - O(1) lookup
symbols: HashMap<SymbolId, Symbol>,
/// Name to ID mapping for human-readable lookups - O(1) lookup
name_to_id: HashMap<String, SymbolId>,
/// Module to symbols mapping for module-specific queries
module_symbols: HashMap<ModuleId, Vec<SymbolId>>,
}
impl SymbolTable {
#[must_use]
pub fn new() -> Self {
Self {
symbols: HashMap::new(),
name_to_id: HashMap::new(),
module_symbols: HashMap::new(),
}
}
/// Adds a symbol to the global table
pub fn add_symbol(&mut self, symbol: Symbol) -> Result<SymbolId, AssembleError> {
let id = symbol.id;
let module_id = symbol.module_id;
let name = symbol.name.clone();
// Check for duplicate names in the same module
if let Some(&existing_id) = self.name_to_id.get(&name)
&& let Some(existing) = self.symbols.get(&existing_id)
&& existing.module_id == module_id
{
return Err(std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
format!("Symbol '{name}' already defined in module"),
)
.into());
}
// Add to all mappings
self.name_to_id.insert(name, id);
self.symbols.insert(id, symbol);
self.module_symbols.entry(module_id).or_default().push(id);
Ok(id)
}
/// Gets the [`Symbol`] by its [`SymbolId`].
#[must_use]
pub fn get(&self, id: &SymbolId) -> Option<&Symbol> {
self.symbols.get(id)
}
/// Gets the [`Symbol`] by its name.
#[must_use]
pub fn get_by_name(&self, name: &str) -> Option<&Symbol> {
self.name_to_id
.get(name)
.and_then(|id| self.symbols.get(id))
}
/// Gets all [`Symbol`]s in a module.
#[must_use]
pub fn get_module_symbols(&self, module_id: &ModuleId) -> Vec<&Symbol> {
self.module_symbols
.get(module_id)
.map(|ids| ids.iter().filter_map(|id| self.symbols.get(id)).collect())
.unwrap_or_default()
}
/// Gets all the public symbols.
#[must_use]
pub fn get_public_symbols(&self) -> Vec<&Symbol> {
self.symbols
.values()
.filter(|sym| matches!(sym.visibility, Visibility::Public))
.collect()
}
/// Updates symbol address (during resolution). Used for flat binaries or symbols with
/// no relocations.
pub fn update_symbol_address(
&mut self,
id: &SymbolId,
address: u32,
) -> Result<(), AssembleError> {
if let Some(symbol) = self.symbols.get_mut(id) {
symbol.address = Some(address);
if symbol.dependencies.is_empty() {
symbol.needs_relocation = false;
}
Ok(())
} else {
Err(
std::io::Error::new(std::io::ErrorKind::NotFound, "Symbol not found")
.into(),
)
}
}
}
impl Default for SymbolTable {
fn default() -> Self {
Self::new()
}
}
+1
View File
@@ -1 +1,2 @@
pub mod brainf;
pub mod project;
+1
View File
@@ -2,6 +2,7 @@
#![allow(unused)]
use std::{fmt, sync::mpsc::Sender};
#[derive(Debug, PartialEq, Eq)]
pub struct Logger {}
impl Logger {
+1 -1
View File
@@ -2,7 +2,7 @@ pub mod logging;
use std::io::Write;
pub fn input(prompt: &str) -> String {
pub fn _input(prompt: &str) -> String {
print!("{prompt}\n > ");
std::io::stdout().flush().expect("Failed to flush stdout");
let mut input = String::new();
+1
View File
@@ -5,3 +5,4 @@ edition.workspace = true
authors.workspace = true
[dependencies]
object = { version = "0.37.1", default-features = false, features = ["elf", "std", "read", "read_core", "write_std", "write", "alloc", "build"] }
+3
View File
@@ -0,0 +1,3 @@
# Common types and methods for the DSA
This library contains the instruction set, encoding and decoding routines, and ELF encoding and loading routines (WIP).
+8
View File
@@ -0,0 +1,8 @@
//! ELF file creation and parsing routines.
use object::{Endianness, build::elf::Builder};
#[allow(clippy::missing_const_for_fn)]
pub fn write() {
let _builder = Builder::new(Endianness::Little, false);
}
+14 -7
View File
@@ -1,8 +1,11 @@
use crate::{instructions::encode::Encode, prelude::*};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
pub enum Interrupt {
Software(u8),
Breakpoint,
#[default]
HardFault,
}
pub type Address = u32;
@@ -10,6 +13,8 @@ pub type Address = u32;
impl Interrupt {
const fn as_u8(self) -> u8 {
match self {
Self::Breakpoint => 0,
Self::HardFault => 1,
Self::Software(code) => code,
}
}
@@ -19,10 +24,11 @@ impl Interrupt {
impl From<u8> for Interrupt {
#[allow(unreachable_code)]
fn from(code: u8) -> Self {
return Self::Software(code);
todo!("Implement this once a hardware interrupt convention is established.");
// Self::Software(_code)
match code {
0 => Self::Breakpoint,
1 => Self::HardFault,
_ => Self::Software(code),
}
}
}
@@ -33,7 +39,7 @@ pub enum InstructionType {
Immediate,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Register {
// general purpose registers
@@ -73,7 +79,8 @@ pub enum 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> {
vec![
Self::Rg0,
+14 -9
View File
@@ -1,4 +1,5 @@
//! Various types of arguments that instructions can take, alongside encoding and decoding logic.
//! Various types of arguments that instructions can take, alongside encoding and decoding
//! logic.
use crate::{
instructions::{RegisterParseError, encode::Encode},
@@ -35,18 +36,20 @@ impl std::fmt::Display for ArgsDecodeError {
impl std::error::Error for ArgsDecodeError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
/// Used by instructions with 2 registers and an immediate argument.
pub struct ITypeArgs {
pub immediate: u16,
pub r1: Register,
/// May not actually be used by some instructions taking an immediate e.g. LUI. This is solved by making the constructor take Options.
/// May not actually be used by some instructions taking an immediate e.g. LUI. This
/// is solved by making the constructor take Options.
pub r2: Register,
}
impl ITypeArgs {
#[must_use]
/// Creates a new [`ITypeArgs`]. If r1 or r2 is unset, they will be replaced with [`Register::NoReg`].
/// Creates a new [`ITypeArgs`]. If r1 or r2 is unset, they will be replaced with
/// [`Register::NoReg`].
pub fn new(immediate: u16, r1: Option<Register>, r2: Option<Register>) -> Self {
let r1 = r1.unwrap_or_default();
let r2 = r2.unwrap_or_default();
@@ -56,8 +59,8 @@ impl ITypeArgs {
}
impl Encode for ITypeArgs {
/// Encodes an I-type instruction from its fields. These must have some unused high-order
/// bits set to 0 else the bit shifting logic gets fucked.
/// Encodes an I-type instruction from its fields. These must have some unused
/// high-order bits set to 0 else the bit shifting logic gets fucked.
fn encode(self, opcode: u8) -> u32 {
let opcode = u32::from(opcode);
let r1 = self.r1 as u32;
@@ -84,7 +87,7 @@ impl TryFrom<u32> for ITypeArgs {
}
/// Used by instructions not using immediates (besides 5 bit shift values).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct RTypeArgs {
pub sr1: Register,
pub sr2: Register,
@@ -95,7 +98,8 @@ pub struct RTypeArgs {
impl RTypeArgs {
#[must_use]
/// Creates a new [`RTypeArgs`]. If any registers are unset, they will be replaced with [`Register::NoReg`]. If `shamt` is unset, it will be set to 0.
/// Creates a new [`RTypeArgs`]. If any registers are unset, they will be replaced
/// with [`Register::NoReg`]. If `shamt` is unset, it will be set to 0.
pub fn new(
sr1: Option<Register>,
sr2: Option<Register>,
@@ -122,7 +126,8 @@ impl Encode for RTypeArgs {
///
/// # Arguments
///
/// - `shamt`: The amount to shift value (used only in shift instructions, otherwise 0).
/// - `shamt`: The amount to shift value (used only in shift instructions, otherwise
/// 0).
fn encode(self, opcode: u8) -> u32 {
let opcode = u32::from(opcode);
let sr1 = self.sr1 as u32;
+3 -1
View File
@@ -39,7 +39,9 @@ impl std::fmt::Display for InstructionDecodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidOpcode(code) => write!(f, "invalid opcode, got {code:x}")?,
Self::InvalidArgument(err) => write!(f, "invalid arguments, got an error {err}")?,
Self::InvalidArgument(err) => {
write!(f, "invalid arguments, got an error {err}")?;
}
}
Ok(())
+1
View File
@@ -12,6 +12,7 @@
clippy::match_wildcard_for_single_variants
)]
pub mod elf;
pub mod instructions;
pub mod prelude {
+6 -5
View File
@@ -160,11 +160,12 @@ impl CodeEditor {
/// Stick to bottom
/// The scroll handle will stick to the bottom position even while the content size
/// changes dynamically. This can be useful to simulate terminal UIs or log/info scrollers.
/// The scroll handle remains stuck until user manually changes position. Once "unstuck"
/// it will remain focused on whatever content viewport the user left it on. If the scroll
/// handle is dragged to the bottom it will again become stuck and remain there until manually
/// pulled from the end position.
/// changes dynamically. This can be useful to simulate terminal UIs or log/info
/// scrollers. The scroll handle remains stuck until user manually changes
/// position. Once "unstuck" it will remain focused on whatever content viewport
/// the user left it on. If the scroll handle is dragged to the bottom it will
/// again become stuck and remain there until manually pulled from the end
/// position.
///
/// **Default: false**
pub fn stick_to_bottom(self, stick_to_bottom: bool) -> Self {
+4 -2
View File
@@ -17,7 +17,6 @@ required-features = ["config"]
common = { path = "../common" }
assembler = { path = "../assembler" }
dsa_editor = { path = "../dsa_editor" }
eframe = { version = "0.31.1" }
egui = "0.31.1"
dirs = "6.0.0"
discord-presence = { version = "1.6.0", optional = true }
@@ -30,7 +29,7 @@ default = ["config"]
discord-rpc = ["dep:discord-presence"]
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]
winit = { version = "0.30.11", features = ["android-native-activity"] }
# jni = "0.21.1"
@@ -38,3 +37,6 @@ winit = { version = "0.30.11", features = ["android-native-activity"] }
[target.'cfg(target_os = "android")'.dependencies.eframe]
version = "0.31.1"
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) {
self.stop();
if let Some(handle) = self.thread_handle.take() {
if let Some(handle) = Arc::into_inner(handle) {
let _ = handle.join();
}
if let Some(handle) = self.thread_handle.take()
&& let Some(handle) = Arc::into_inner(handle)
{
let _ = handle.join();
}
}
}
+120 -94
View File
@@ -1,15 +1,12 @@
use std::sync::Arc;
use std::{
sync::mpsc::{self, Receiver, Sender},
thread,
time::Duration,
};
use std::sync::mpsc::{self, Receiver, Sender};
#[allow(unused_imports)]
use crate::emulator::misc::rpc::{Activity, RpcClient};
use crate::emulator::system::model::StateUpdate;
use crate::emulator::system::{
model::{Command, PersistentState, Running, State},
model::{Command, Running},
processor::Processor,
};
@@ -19,28 +16,33 @@ use common::prelude::*;
#[allow(unused_variables)]
pub fn run_emulator(
cmd_rx: &Receiver<Command>,
state_tx: &Sender<State>,
state_tx: &Sender<StateUpdate>,
mut processor: Processor,
rpc_client: Option<&Arc<RpcClient>>,
) {
println!("INFO: Starting emulator.");
let mut running = Running::Paused;
let mut addr = 0u32;
let mut step = 0;
let mut addr;
let mut history = Vec::<(u32, Instruction)>::new();
let size = 256;
let memory_view = processor.memory.read_range(addr, size);
let initial_state = state(&mut processor, running, 0, memory_view, &mut history);
let _ = state_tx.send(initial_state);
state_tx
.send(StateUpdate::Running(Running::Paused))
.expect("Failed to send initial state!");
let mut instruction_count = 0;
let mut update = false;
loop {
let cmd = if running == Running::Running {
let cmd = if running == Running::Running || step > 0 {
match cmd_rx.try_recv() {
Ok(cmd) => Some(cmd),
Err(mpsc::TryRecvError::Empty) => None,
Err(mpsc::TryRecvError::Empty) => {
update = false;
None
}
Err(mpsc::TryRecvError::Disconnected) => break,
}
} else {
@@ -91,52 +93,120 @@ pub fn run_emulator(
processor.reset();
}
Command::Step => {
running = Running::Paused;
// Execute one cycle.
match processor.cycle() {
Ok((addr, instruction)) => {
history.push((addr, instruction));
}
Err(why) => {
let pcx = processor.get(Register::Pcx);
eprintln!(
"Could not decode instruction at {pcx:x}. Reason: {why}"
);
continue;
}
}
instruction_count += 1;
}
Command::Read(new, _size) => {
addr = new;
Command::Step(x) => {
step = x;
}
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) => {
update = true;
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);
let state = state(
&mut processor,
running,
instruction_count,
memory_view,
&mut history,
);
if step > 0 {
step -= 1;
update = true;
running = Running::Paused;
println!("state");
let _ = state_tx.send(state);
// Execute one cycle.
match processor.cycle() {
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 {
let mut update = false;
update = true;
// Execute one cycle.
let instruction = match processor.cycle() {
@@ -149,60 +219,16 @@ pub fn run_emulator(
};
history.push(instruction);
// let instruction = match Instruction::decode(cpu_lock.get(Register::Cir))
// {};
if matches!(instruction.1, Instruction::Halt) {
running = Running::Halted;
update = true;
}
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(
cpu_lock: &mut Processor,
running: Running,
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 },
}
fn report_err(state_tx: &Sender<StateUpdate>, why: &str, processor: &mut Processor) {
processor.begin_interrupt(Interrupt::HardFault);
let _ = state_tx.send(StateUpdate::Error(why.to_string()));
}
+71 -23
View File
@@ -1,13 +1,43 @@
use std::collections::HashMap;
use crate::emulator::system::model::ProcessorError;
pub trait MemoryUnit: Send + Sync {
fn reset(&mut self);
fn read_byte(&mut self, addr: u32) -> u8;
fn write_byte(&mut self, addr: u32, value: u8);
fn read_word(&mut self, addr: u32) -> u32;
fn write_word(&mut self, addr: u32, value: u32);
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8>;
fn write_range(&mut self, addr: u32, value: Vec<u8>);
fn read_byte(&mut self, addr: u32) -> Result<u8, ProcessorError>;
fn write_byte(&mut self, addr: u32, value: u8) -> Result<(), ProcessorError>;
fn read_word(&mut self, addr: u32) -> Result<u32, ProcessorError>;
fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError>;
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 {
@@ -64,59 +94,77 @@ impl MemoryUnit for MainStore {
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 = 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);
println!("reading word from {block_addr:x?} + {offset}");
let block = self.mut_block(block_addr);
let mut bytes = [0; 4];
bytes[0] = block.data[offset as usize];
bytes[1] = block.data[(offset + 1) as usize];
bytes[2] = block.data[(offset + 2) as usize];
bytes[3] = block.data[(offset + 3) as usize];
u32::from_be_bytes(bytes)
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);
for i in 0..size {
data.push(self.read_byte(addr + i));
data.push(self.read_byte(addr + i)?);
}
// println!("reading {data:?} from {addr:x?}");
data
Ok(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 = self.mut_block(block_addr);
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 = self.mut_block(block_addr);
block.data[offset as usize] = (value >> 24) as u8;
block.data[(offset + 1) as usize] = (value >> 16) as u8;
block.data[(offset + 2) as usize] = (value >> 8) as u8;
block.data[(offset + 3) as usize] = value as u8;
Ok(())
}
fn write_range(&mut self, addr: u32, value: Vec<u8>) {
// println!("writing {value:?} to {addr:x?}");
fn write_range(&mut self, addr: u32, value: Vec<u8>) -> Result<(), ProcessorError> {
for (i, byte) in value.into_iter().enumerate() {
let (block_addr, offset) = Self::segment_addr(addr + i as u32);
let block = self.mut_block(block_addr);
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::*;
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
@@ -16,15 +18,143 @@ pub trait IODevice: Send + Sync {
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Command {
// set emulator state.
Start,
Stop,
Step,
Step(usize),
Reset(usize),
Interrupt(Interrupt),
// Performs direct read/write operations on the emulator's memory.
Read(Address, u32),
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)]
@@ -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::{
memory::MemoryUnit,
model::{IODevice, RegFile},
model::{IODevice, ProcessorError, RegFile},
};
use common::instructions::{
Instruction, Interrupt, Register, errors::InstructionDecodeError,
};
use common::instructions::{Instruction, Interrupt, Register};
pub struct Processor {
pub memory: Box<dyn MemoryUnit>,
@@ -21,11 +19,11 @@ pub struct Processor {
pub dustbin: u32,
}
#[expect(dead_code)]
fn log(message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}");
}
#[allow(clippy::needless_pass_by_ref_mut)]
impl Processor {
#[must_use]
pub fn new(memory: Box<dyn MemoryUnit>, io_devices: Vec<Arc<dyn IODevice>>) -> Self {
@@ -48,7 +46,7 @@ impl Processor {
self.memory.reset();
}
pub fn cycle(&mut self) -> Result<(u32, Instruction), InstructionDecodeError> {
pub fn cycle(&mut self) -> Result<(u32, Instruction), ProcessorError> {
self.halted = false;
// Get value from PCX.
@@ -58,18 +56,16 @@ impl Processor {
// Set MAR to the previous value of PCX.
*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].
*self.reg(Register::Mar) = val;
// Decode and execute the instruction.
let instruction = Instruction::decode(val)?;
log(&instruction.to_string());
instruction.execute(self);
let instruction = Instruction::decode(val)
.map_err(|_| ProcessorError::InvalidInstruction(val))?;
instruction.execute(self)?;
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)
}
@@ -99,21 +95,7 @@ impl Processor {
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
fn set_flag(&mut self, flag: Flag, value: bool) {
if value {
*self.reg(Register::Sts) |= flag as u32;
@@ -135,16 +117,18 @@ impl Processor {
*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.
todo!();
}
// TODO: remove this once implemented
#[allow(clippy::needless_pass_by_ref_mut)]
fn end_interrupt(&mut self) {
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 size = n * 4;
// returns the stack
@@ -170,12 +154,12 @@ enum Flag {
}
trait Executable {
fn execute(self, cpu: &mut Processor);
fn execute(self, cpu: &mut Processor) -> Result<(), ProcessorError>;
}
impl Executable for Instruction {
#[allow(clippy::too_many_lines)]
fn execute(self, cpu: &mut Processor) {
fn execute(self, cpu: &mut Processor) -> Result<(), ProcessorError> {
match self {
// No operation - a blank line.
// Copies from SrcReg to a.drReg.
@@ -193,7 +177,8 @@ impl Executable for Instruction {
// effective address must be byte-aligned.
Self::LoadByte(a) => {
*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.
Self::LoadByteSigned(a) => {
*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) => {
// we read an entire word, then right shift so we only get the first half
// of the word
*cpu.reg(a.r2) =
cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate)) >> 16;
*cpu.reg(a.r2) = cpu
.memory
.read_word(cpu.get(a.r1) + u32::from(a.immediate))?
>> 16;
}
// Loads a sign-extended half-word from memory address (base + offset) into
// a.drReg. The effective address must be 2-byte-aligned.
Self::LoadHalfwordSigned(a) => {
*cpu.reg(a.r2) = sign_extend(
cpu.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
// effective address must be 4-byte-aligned.
Self::LoadWord(a) => {
*cpu.reg(a.r2) =
cpu.memory.read_word(cpu.get(a.r1) + u32::from(a.immediate));
*cpu.reg(a.r2) = cpu
.memory
.read_word(cpu.get(a.r1) + u32::from(a.immediate))?;
}
// 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.get(a.r2) + u32::from(a.immediate),
cpu.get(a.r1) as u8,
);
)?;
}
// 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
let bytes = (cpu.get(a.r1) as u16).to_le_bytes();
cpu.memory
.write_byte(cpu.get(a.r2) + u32::from(a.immediate), bytes[0]);
.write_byte(cpu.get(a.r2) + u32::from(a.immediate), bytes[0])?;
cpu.memory
.write_byte(cpu.get(a.r2) + u32::from(a.immediate) + 1, bytes[1]);
.write_byte(cpu.get(a.r2) + u32::from(a.immediate) + 1, bytes[1])?;
}
// Stores a word from SrcReg in memory address (base + offset) The effective
// address must be 4-byte-aligned.
Self::StoreWord(a) => {
cpu.memory
.write_word(cpu.get(a.r2) + u32::from(a.immediate), cpu.get(a.r1));
.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
@@ -411,6 +402,7 @@ impl Executable for Instruction {
todo!()
}
}
Ok(())
}
}
+110 -38
View File
@@ -13,7 +13,9 @@ fn test_nop_instruction() {
let mut cpu = create_test_processor();
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!(
cpu.registers.get(Register::Rg0),
@@ -37,7 +39,9 @@ fn test_mov_instruction() {
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);
}
@@ -53,7 +57,9 @@ fn test_mov_signed_instruction() {
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);
}
@@ -61,7 +67,9 @@ fn test_mov_signed_instruction() {
fn test_load_byte_instruction() {
let mut cpu = create_test_processor();
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;
let load_byte_instr = Instruction::LoadByte(ITypeArgs::new(
@@ -70,7 +78,9 @@ fn test_load_byte_instruction() {
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);
}
@@ -78,7 +88,9 @@ fn test_load_byte_instruction() {
fn test_load_byte_signed_instruction() {
let mut cpu = create_test_processor();
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;
let load_byte_signed_instr = Instruction::LoadByteSigned(ITypeArgs::new(
@@ -87,7 +99,9 @@ fn test_load_byte_signed_instruction() {
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);
}
@@ -95,7 +109,9 @@ fn test_load_byte_signed_instruction() {
fn test_load_halfword_instruction() {
let mut cpu = create_test_processor();
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;
let load_halfword_instr = Instruction::LoadHalfword(ITypeArgs::new(
@@ -104,7 +120,9 @@ fn test_load_halfword_instruction() {
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);
}
@@ -112,7 +130,9 @@ fn test_load_halfword_instruction() {
fn test_load_word_instruction() {
let mut cpu = create_test_processor();
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;
let load_word_instr = Instruction::LoadWord(ITypeArgs::new(
@@ -121,7 +141,9 @@ fn test_load_word_instruction() {
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);
}
@@ -138,8 +160,10 @@ fn test_store_byte_instruction() {
Some(Register::Rg1),
));
store_byte_instr.execute(&mut cpu);
assert_eq!(cpu.memory.read_byte(addr), 0xAB);
store_byte_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction",
);
assert_eq!(cpu.memory.read_byte(addr).expect("Emulator was slain by losing the game while attempting to execute instruction"), 0xAB);
}
#[test]
@@ -155,8 +179,10 @@ fn test_store_word_instruction() {
Some(Register::Rg1),
));
store_word_instr.execute(&mut cpu);
assert_eq!(cpu.memory.read_word(addr), 0x1234_5678);
store_word_instr.execute(&mut cpu).expect(
"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]
@@ -172,7 +198,9 @@ fn test_add_instruction() {
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);
}
@@ -189,7 +217,9 @@ fn test_sub_instruction() {
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);
}
@@ -206,7 +236,9 @@ fn test_and_instruction() {
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);
}
@@ -223,7 +255,9 @@ fn test_or_instruction() {
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);
}
@@ -240,7 +274,9 @@ fn test_xor_instruction() {
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);
}
@@ -256,7 +292,9 @@ fn test_not_instruction() {
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);
}
@@ -273,7 +311,9 @@ fn test_compare_equal() {
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::GreaterThan));
@@ -293,7 +333,9 @@ fn test_compare_greater_than() {
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::GreaterThan));
@@ -313,7 +355,9 @@ fn test_compare_less_than() {
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::GreaterThan));
@@ -328,7 +372,9 @@ fn test_increment_instruction() {
let inc_instr =
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);
}
@@ -340,7 +386,9 @@ fn test_decrement_instruction() {
let dec_instr =
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);
}
@@ -356,7 +404,9 @@ fn test_shift_left_with_shamt() {
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);
}
@@ -372,7 +422,9 @@ fn test_shift_right_with_shamt() {
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);
}
@@ -389,7 +441,9 @@ fn test_shift_left_with_register() {
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);
}
@@ -403,7 +457,9 @@ fn test_load_lower_immediate() {
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);
}
@@ -418,7 +474,9 @@ fn test_load_upper_immediate() {
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);
}
@@ -430,7 +488,9 @@ fn test_jump_unconditional() {
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_ne!(cpu.get(Register::Pcx), initial_pc);
}
@@ -444,7 +504,9 @@ fn test_jump_equal_when_flag_set() {
let jump_eq_instr =
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);
}
@@ -458,7 +520,9 @@ fn test_jump_equal_when_flag_not_set() {
let jump_eq_instr =
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);
}
@@ -467,7 +531,9 @@ fn test_halt_instruction() {
let mut cpu = create_test_processor();
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);
}
@@ -484,7 +550,9 @@ fn test_nand_instruction() {
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);
}
@@ -501,7 +569,9 @@ fn test_nor_instruction() {
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);
}
@@ -518,6 +588,8 @@ fn test_xnor_instruction() {
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);
}
+58 -22
View File
@@ -1,5 +1,3 @@
use std::sync::mpsc::Sender;
use crate::emulator::{
system::model::{Command, Running, State},
ui::interface::Component,
@@ -9,19 +7,27 @@ use common::{instructions::Register, prelude::Instruction};
pub struct ControlPanel {
visible: bool,
sender: Sender<Command>,
step_amount_input: String,
step_amount: usize,
}
impl ControlPanel {
#[must_use]
pub const fn new(sender: Sender<Command>) -> Self {
#[allow(clippy::must_use_candidate)]
pub fn new() -> Self {
Self {
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 {
fn category(&self) -> super::interface::Category {
super::interface::Category::Control
@@ -47,46 +53,76 @@ impl Component for ControlPanel {
.clicked()
{
if state.running == Running::Running {
self.sender.send(Command::Stop).unwrap_or_else(|_| {
state.error = Some("Failed to send command".to_string());
state.cmd_sender.send(Command::Stop).unwrap_or_else(|_| {
state.error_log.push("Failed to send command".to_string());
});
} else {
self.sender.send(Command::Start).unwrap_or_else(|_| {
state.error = Some("Failed to send command".to_string());
state.cmd_sender.send(Command::Start).unwrap_or_else(|_| {
state.error_log.push("Failed to send command".to_string());
});
}
}
// Step
if ui.button("Step").clicked() {
self.sender.send(Command::Step).unwrap_or_else(|_| {
state.error = Some("Failed to send command".to_string());
});
state
.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
if ui.button("Reset All").clicked() {
self.sender.send(Command::Reset(0)).unwrap_or_else(|_| {
state.error = Some("Failed to send command".to_string());
});
state
.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
if ui.button("Clear Registers").clicked() {
self.sender.send(Command::Reset(1)).unwrap_or_else(|_| {
state.error = Some("Failed to send command".to_string());
});
state
.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
if ui.button("Clear RAM").clicked() {
self.sender.send(Command::Reset(2)).unwrap_or_else(|_| {
state.error = Some("Failed to send command".to_string());
});
state
.cmd_sender
.send(Command::Reset(2))
.unwrap_or_else(|_| {
state.error_log.push("Failed to send command".to_string());
});
}
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
ui.label(format!(
"Status: {}",
+3 -1
View File
@@ -1,5 +1,5 @@
use crate::emulator::{
system::model::State,
system::model::{Command, State},
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) {
state.send(Command::DisplayRequest);
let display: Vec<u8> = state.display_view.clone();
let font_id = FontId::monospace(12.0);
+79 -138
View File
@@ -3,9 +3,9 @@ use std::{
ffi::OsStr,
fs,
path::{Path, PathBuf},
sync::mpsc::Sender,
};
use assembler::compiler_engine::CompilerEngine;
use common::prelude::Instruction;
use egui::{Align, Context, Key, Layout, Ui};
@@ -17,8 +17,9 @@ use crate::emulator::{
ui::interface::Component,
};
use assembler::prelude::*;
// use assembler::prelude::*;
#[derive(Default)]
pub struct Editor {
// editor state
path: Option<PathBuf>,
@@ -41,7 +42,6 @@ pub struct Editor {
// other
visible: bool,
sender: Sender<Command>,
error: Option<String>,
}
@@ -94,14 +94,13 @@ impl Component for Editor {
impl Editor {
#[must_use]
pub const fn new(sender: Sender<Command>) -> Self {
pub const fn new() -> Self {
Self {
path: None,
text: String::new(),
buffer: String::new(),
output: Vec::new(),
unsaved: true,
sender,
cursor_col: 1,
cursor_line: 1,
visible: false,
@@ -199,38 +198,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() {
// TODO: Flash an error stating you can only have one menu open at once.
self.save_file_dialog = None;
@@ -252,119 +219,92 @@ impl Editor {
fn handle_file_dialogs(&mut self, ctx: &egui::Context) {
// Handle open dialog
if let Some(dialog) = &mut self.open_file_dialog {
if 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) => {
let mut res = String::new();
for (i, b) in content.iter().enumerate() {
_ = write!(res, "{b:02x}");
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;
}
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) => {
let mut res = String::new();
for (i, b) in content.iter().enumerate() {
_ = write!(res, "{b:02x}");
if i % 4 == 3 {
res.push('\n');
}
}
}
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.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 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) {
// Output area with synchronized scrolling
egui::ScrollArea::vertical()
@@ -526,7 +466,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);
ui.horizontal(|ui| {
@@ -567,7 +507,8 @@ impl Editor {
Some("Can't load program at invalid offset!".to_string());
}
self.sender
state
.cmd_sender
.send(Command::Write(self.load_offset, self.output.clone()))
.unwrap_or_else(|_| {
self.error = Some("Failed to send command".to_string());
+8 -3
View File
@@ -1,6 +1,9 @@
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 {
visible: bool,
@@ -20,11 +23,13 @@ impl Component for History {
}
fn render(&mut self, state: &mut State, ui: &mut Ui, _ctx: &Context) {
state.send(Command::HistoryRequest);
egui::ScrollArea::vertical()
.id_salt("output_scroll")
.max_width(400.0)
.show(ui, |ui| {
if state.persistent.history.is_empty() {
if state.instruction_history.is_empty() {
ui.label(
egui::RichText::new("No output data")
.font(egui::FontId::monospace(12.0))
@@ -40,7 +45,7 @@ impl Component for History {
.show(ui, |ui| {
// Process bytes in chunks of 4
for (idx, instruction) in
state.persistent.history.iter().enumerate()
state.instruction_history.iter().enumerate()
{
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};
pub trait Component {
@@ -34,21 +34,15 @@ impl Category {
}
pub struct EmulatorUI {
pub sender: Sender<Command>,
pub receiver: Receiver<State>,
pub state: State,
pub persistent: PersistentState,
pub components: Vec<Box<dyn Component>>,
}
impl EmulatorUI {
#[must_use]
pub fn new(sender: Sender<Command>, receiver: Receiver<State>) -> Self {
pub fn new(sender: Sender<Command>, receiver: Receiver<StateUpdate>) -> Self {
Self {
sender,
receiver,
state: State::default(),
persistent: PersistentState::default(),
state: State::new(sender, receiver),
components: vec![],
}
}
@@ -56,19 +50,13 @@ impl EmulatorUI {
pub fn add_component(&mut self, component: Box<dyn 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 {
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 {
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;
@@ -7,23 +7,22 @@ use crate::emulator::{
ui::interface::Component,
};
#[derive(Default)]
pub struct MemoryInspector {
view_size: u32,
view_addr: u32,
visible: bool,
addr_input: String,
sender: Sender<Command>,
}
impl MemoryInspector {
#[must_use]
pub const fn new(sender: Sender<Command>) -> Self {
pub const fn new() -> Self {
Self {
view_size: 256,
view_addr: 0,
visible: false,
addr_input: String::new(),
sender,
}
}
}
@@ -63,28 +62,26 @@ impl Component for MemoryInspector {
let search_clicked = ui.button("🔍 Search").clicked();
// Handle Enter key in text field
let enter_pressed =
address_response.lost_focus() && ctx.input(|i| i.key_pressed(egui::Key::Enter));
let enter_pressed = address_response.lost_focus()
&& ctx.input(|i| i.key_pressed(egui::Key::Enter));
if search_clicked || enter_pressed {
if let Ok(new) = parse_address(&self.addr_input) {
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 {
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)");
});
// 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}"));
}
@@ -113,9 +110,12 @@ impl Component for MemoryInspector {
ui.end_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);
ui.monospace(format!("0x{row_address:08X} ({row_address})"));
ui.monospace(format!(
"0x{row_address:08X} ({row_address})"
));
for &byte in chunk {
ui.monospace(format!("{byte:02X}"));
}
@@ -126,12 +126,16 @@ impl Component for MemoryInspector {
}
// combine all 4 bytes in the chunk into a u32
let combined = chunk
.iter()
.fold(0u32, |acc, &byte| (acc << 8) | u32::from(byte));
let combined = chunk.iter().fold(0u32, |acc, &byte| {
(acc << 8) | u32::from(byte)
});
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();
}
+1
View File
@@ -3,6 +3,7 @@ pub mod display;
pub mod editor;
pub mod history;
pub mod interface;
pub mod loader;
pub mod memory_inspector;
pub mod menu;
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;
@@ -33,6 +36,8 @@ impl Component for StackInspector {
}
fn render(&mut self, state: &mut State, ui: &mut egui::Ui, _ctx: &egui::Context) {
state.send(Command::StackRequest);
ui.vertical(|ui| {
ui.heading("Stack Inspector");
egui::ScrollArea::vertical()
+10 -7
View File
@@ -30,7 +30,7 @@ use crate::emulator::{
system::{
emulator::run_emulator,
memory::MainStore,
model::{Command, State},
model::{Command, StateUpdate},
processor::Processor,
},
ui::{
@@ -86,7 +86,7 @@ pub fn android_main(app: AndroidApp) -> Result<(), Box<dyn std::error::Error>> {
pub fn setup_emulator(
cmd_receiver: Receiver<Command>,
state_sender: Sender<State>,
state_sender: Sender<StateUpdate>,
rpc_client: Option<Arc<RpcClient>>,
) {
let main_store = MainStore::new();
@@ -101,22 +101,22 @@ pub fn setup_emulator(
#[must_use]
pub fn setup_ui(
cmd_sender: Sender<Command>,
state_reciever: Receiver<State>,
state_reciever: Receiver<StateUpdate>,
) -> EmulatorUI {
let mut ui = EmulatorUI::new(cmd_sender.clone(), state_reciever);
let mut ui = EmulatorUI::new(cmd_sender, state_reciever);
// Create UI modules.
let control_unit = ControlPanel::new(cmd_sender.clone());
let control_unit = ControlPanel::new();
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));
let stack_inspector = StackInspector::new();
ui.add_component(Box::new(stack_inspector));
let editor = Editor::new(cmd_sender);
let editor = Editor::new();
ui.add_component(Box::new(editor));
let display = Display::new();
@@ -125,5 +125,8 @@ pub fn setup_ui(
let history = emulator::ui::history::History::new();
ui.add_component(Box::new(history));
let loader = emulator::ui::loader::Loader::new();
ui.add_component(Box::new(loader));
ui
}
+39 -21
View File
@@ -5,12 +5,40 @@
include print "./lib/print.dsa"
// "print hello world"
db program: "++++++[>++++++++++++<-]>.>++++++++++[>++++++++++<-]>+.+++++++..+++.>++++[>+++++++++++<-]>.<+++[>----<-]>.<<<<<+++[>+++++<-]>.>>.+++.------.--------.>>+."
db program "++++++++++++++++++++++++++++++++++++++++++++
>++++++++++++++++++++++++++++++++
>++++++++++++++++
>
>+
<<
[
>>
>
>++++++++++
<<
[->+>-[>+>>]>[+[-<+>]>+>>]<<<<<<]
>[<+>-]
>[-]
>>
>++++++++++
<
[->-[>+>>]>[+[-<+>]>+>>]<<<<<]
>[-]
>>[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]
<[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]
<<<++++++++++++++++++++++++++++++++++++++++++++++++.[-]
<<<<<<<.>.
>>[>>+<<-]
>[>+<<+>-]
>[<+>-]
<<<-
]
<<++..."
db error: "Invalid Instruction!"
dw stack: 0x10000
dw input: 0x30000
resb data: 1024
db error "Invalid Instruction!"
dw stack 0x10000
dw input 0x30000
resb data 1024
// set up a stack so we can call functions
_init_stack:
@@ -20,6 +48,7 @@ _init_stack:
start:
// load the start of the program into rg0
lwi program, rg0
lwi data, rg1
// rg0 is our instruction pointer
// rg1 is our data pointer
@@ -40,13 +69,6 @@ loop_start:
// load the current instruction into rg3
ldb rg0, rg3
// pusha 2
// push rg3
// call print::print_byte
// pop zero
// popa 2
// switch on the instruction
// all cases will return to either loop_start or loop_end
cmp rg3, rg8
@@ -68,19 +90,15 @@ loop_start:
cmp rg3, zero
jeq end
// if we get here, we don't know what the instruction is
lwi error, rg0
push rg0
call print::print
pop zero
end:
lwi error, rg2
// if we get here, we don't know what the instruction is
lwi error, rg2
pusha 2
push rg2
call print::print
pop zero
popa 2
end:
hlt
loop_end:
@@ -110,7 +128,7 @@ inc_ptr:
// ------------------------------------------
// decrement the pointer
dec_ptr:
stw rg1, rg2
stw rg2, rg1
subi rg1, 4
ldw rg1, rg2
jmp loop_end