mirror of
https://github.com/kaesaecracker/echse.git
synced 2025-02-22 16:57:11 +01:00
initial commit
This commit is contained in:
commit
5ad6baa30d
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
target
|
||||
tarpaulin-report.html
|
456
Cargo.lock
generated
Normal file
456
Cargo.lock
generated
Normal file
|
@ -0,0 +1,456 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"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 = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ariadne"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31beedec3ce83ae6da3a79592b3d8d7afd146a5b15bb9bb940279aced60faa89"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chumsky"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
"stacker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "echse"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ariadne",
|
||||
"chumsky",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "echse_cute"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"echse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "krabbeltier"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"echse",
|
||||
"shellwords",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.168"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psm"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[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 = "shellwords"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89e515aa4699a88148ed5ef96413ceef0048ce95b43fbc955a33bde0a70fcae6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "stacker"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"psm",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
[workspace]
|
||||
members = ["echse_cute", "krabbeltier", "echse"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
clap = { version = "4.5.23", features = ["derive"] }
|
||||
shellwords = { version = "1.1.0" }
|
||||
ariadne = { version = "0.5.0", features = ["auto-color"] }
|
48
README.md
Normal file
48
README.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
# echse
|
||||
|
||||
The cutest assembly-like language!
|
||||
|
||||
This is for learning or teaching purposes only - and is probably not useful for anything else.
|
||||
|
||||
## From code to running
|
||||
|
||||
1. Code with macros (.es)
|
||||
2. Preprocessor applies macros and resolves jump labels (.ei)
|
||||
3. Transform into binary representation (.eo)
|
||||
4. Run the binary
|
||||
|
||||
Currently, you have to write and execute a .ei file.
|
||||
|
||||
## Ideas and ToDos
|
||||
|
||||
- a cute manual for the provided functionality
|
||||
- preprocessor that supports jumping to labels and unrolled loops
|
||||
- separate machine configuration and program in files
|
||||
- register custom instructions before parsing
|
||||
- optional stack with pus, pop, ret
|
||||
- small games built upon this:
|
||||
- battle multiple programs against each other
|
||||
- rustlings-like exercises to learn the instructions
|
||||
- put multiple echsen into an environment, interact with environment
|
||||
|
||||
## Limitations
|
||||
|
||||
List of constraints that I want to keep:
|
||||
|
||||
- general-purpose registers
|
||||
- are always interpreted as native signed integers
|
||||
- are numbered r0...rn, without gaps
|
||||
- all base instruction
|
||||
- have names with 3 letters
|
||||
- all test instructions start with i (after prefixes) and always set r0
|
||||
- all conditional jumps act based upon r0
|
||||
- when interpreting numbers as booleans: 0 is false, everything else is true. The name of instructions should reflect that.
|
||||
- all extension instructions
|
||||
- have names longer than 3 letters
|
||||
- have a extension prefix
|
||||
- the executed instructions do not know anything about the preprocessor and all formatting and jump marks are lost
|
||||
- there should be no hard-coded calling convention
|
||||
|
||||
## License
|
||||
|
||||
The code in this repository is licensed under GPL v2 or later, unless stated otherwise in the file.
|
13
all_instructions.ei
Normal file
13
all_instructions.ei
Normal file
|
@ -0,0 +1,13 @@
|
|||
add 1 2 r0
|
||||
add r0 r1 r0
|
||||
mod r0 10 r1
|
||||
div 100 r1 r1
|
||||
nop
|
||||
put 42 r1
|
||||
put r1 r0
|
||||
sub 10 5 r1
|
||||
sub r1 r1 r1
|
||||
teq r1 r1
|
||||
tlt r0 r1
|
||||
tze r1
|
||||
jze r0 r1
|
14
echse/Cargo.toml
Normal file
14
echse/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "echse"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
chumsky = "0.9.3"
|
||||
ariadne = {version = "0.5.0", optional = true}
|
||||
|
||||
[features]
|
||||
default = ["error-report"]
|
||||
error-report = ["dep:ariadne"]
|
135
echse/src/instructions/add_instruction.rs
Normal file
135
echse/src/instructions/add_instruction.rs
Normal file
|
@ -0,0 +1,135 @@
|
|||
use crate::instructions::{
|
||||
try_load_binary_op, Instruction, InstructionName, ParametersError, TryLoadInstruction,
|
||||
};
|
||||
use crate::machine::{Machine, RegisterIndex, RegisterOrValue};
|
||||
use std::fmt::Display;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Addition {
|
||||
/// first input
|
||||
pub a: RegisterOrValue,
|
||||
/// second input
|
||||
pub b: RegisterOrValue,
|
||||
/// result register
|
||||
pub r: RegisterIndex,
|
||||
}
|
||||
|
||||
impl Instruction for Addition {
|
||||
fn execute(&self, machine: &mut Machine) {
|
||||
let value = self.a.read(machine) + self.b.read(machine);
|
||||
self.r.write(machine, value);
|
||||
}
|
||||
}
|
||||
|
||||
impl InstructionName for Addition {
|
||||
const INSTRUCTION_NAME: &'static str = "add";
|
||||
}
|
||||
|
||||
impl Display for Addition {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
"{} {} {} {}",
|
||||
Self::INSTRUCTION_NAME,
|
||||
self.a,
|
||||
self.b,
|
||||
self.r
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryLoadInstruction for Addition {
|
||||
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
|
||||
let (a, b, r) = try_load_binary_op(params)?;
|
||||
Ok(Rc::new(Self { a, b, r }))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::instructions::{Addition, Instruction, ParametersError, TryLoadInstruction};
|
||||
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
|
||||
|
||||
#[test]
|
||||
fn execute() {
|
||||
let r0 = RegisterIndex::new(0);
|
||||
let r1 = RegisterIndex::new(1);
|
||||
let mut machine = MachineBuilder::new()
|
||||
.with_registers(2)
|
||||
.without_instructions()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(machine.registers.read_all(), vec![0, 0]);
|
||||
Instruction::execute(
|
||||
&Addition {
|
||||
a: RegisterOrValue::Value(42),
|
||||
b: RegisterOrValue::Value(23),
|
||||
r: r0,
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![65, 0]);
|
||||
Instruction::execute(
|
||||
&Addition {
|
||||
a: RegisterOrValue::Register(r0),
|
||||
b: RegisterOrValue::Value(-23),
|
||||
r: r1,
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![65, 42]);
|
||||
Instruction::execute(
|
||||
&Addition {
|
||||
a: RegisterOrValue::Value(1337),
|
||||
b: RegisterOrValue::Register(r1),
|
||||
r: r1,
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![65, 1379]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load() {
|
||||
assert!(matches!(
|
||||
Addition::try_load(&[]),
|
||||
Err(ParametersError::UnexpectedCount {
|
||||
found: 0,
|
||||
expected: 3
|
||||
})
|
||||
));
|
||||
assert!(matches!(
|
||||
Addition::try_load(&[
|
||||
RegisterOrValue::Value(42),
|
||||
RegisterOrValue::Value(23),
|
||||
RegisterOrValue::Value(1337),
|
||||
]),
|
||||
Err(ParametersError::UnexpectedType { index: 2 })
|
||||
));
|
||||
assert!(matches!(
|
||||
Addition::try_load(&[
|
||||
RegisterOrValue::Value(42),
|
||||
RegisterOrValue::Value(23),
|
||||
RegisterOrValue::Register(RegisterIndex::R0),
|
||||
]),
|
||||
Ok(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print() {
|
||||
assert_eq!(
|
||||
"add 1337 r0 r1",
|
||||
format!(
|
||||
"{}",
|
||||
Addition {
|
||||
a: RegisterOrValue::Value(1337),
|
||||
b: RegisterOrValue::Register(RegisterIndex::R0),
|
||||
r: RegisterIndex(1),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
47
echse/src/instructions/collection.rs
Normal file
47
echse/src/instructions/collection.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use crate::instructions::*;
|
||||
use std::fmt::Display;
|
||||
use std::ops::Index;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Instructions(Vec<Rc<dyn Instruction>>);
|
||||
|
||||
impl Instructions {
|
||||
pub fn empty() -> Self {
|
||||
Self(vec![])
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &Rc<dyn Instruction>> {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Rc<dyn Instruction>>> for Instructions {
|
||||
fn from(instructions: Vec<Rc<dyn Instruction>>) -> Self {
|
||||
Self(instructions)
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Instructions {
|
||||
type Output = Rc<dyn Instruction>;
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Instructions {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for (index, instr) in self.iter().enumerate() {
|
||||
f.write_fmt(format_args!("{index:03}: {instr}\n"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
135
echse/src/instructions/div_instruction.rs
Normal file
135
echse/src/instructions/div_instruction.rs
Normal file
|
@ -0,0 +1,135 @@
|
|||
use crate::instructions::{
|
||||
try_load_binary_op, Instruction, InstructionName, ParametersError, TryLoadInstruction,
|
||||
};
|
||||
use crate::machine::{Machine, RegisterIndex, RegisterOrValue};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Division {
|
||||
/// first input
|
||||
pub a: RegisterOrValue,
|
||||
/// second input
|
||||
pub b: RegisterOrValue,
|
||||
/// result register
|
||||
pub r: RegisterIndex,
|
||||
}
|
||||
|
||||
impl Instruction for Division {
|
||||
fn execute(&self, machine: &mut Machine) {
|
||||
let value = self.a.read(machine) / self.b.read(machine);
|
||||
self.r.write(machine, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl InstructionName for Division {
|
||||
const INSTRUCTION_NAME: &'static str = "div";
|
||||
}
|
||||
|
||||
impl Display for Division {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
"{} {} {} {}",
|
||||
Self::INSTRUCTION_NAME,
|
||||
self.a,
|
||||
self.b,
|
||||
self.r
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryLoadInstruction for Division {
|
||||
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
|
||||
let (a, b, r) = try_load_binary_op(params)?;
|
||||
Ok(Rc::new(Self { a, b, r }))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::instructions::{Division, Instruction, ParametersError, TryLoadInstruction};
|
||||
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
|
||||
|
||||
#[test]
|
||||
fn execute() {
|
||||
let r0 = RegisterIndex::new(0);
|
||||
let r1 = RegisterIndex::new(1);
|
||||
let mut machine = MachineBuilder::new()
|
||||
.with_registers(2)
|
||||
.without_instructions()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(machine.registers.read_all(), vec![0, 0]);
|
||||
Instruction::execute(
|
||||
&Division {
|
||||
a: RegisterOrValue::Value(42),
|
||||
b: RegisterOrValue::Value(10),
|
||||
r: r0,
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![4, 0]);
|
||||
Instruction::execute(
|
||||
&Division {
|
||||
a: RegisterOrValue::Register(r0),
|
||||
b: RegisterOrValue::Value(-1),
|
||||
r: r1,
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![4, -4]);
|
||||
Instruction::execute(
|
||||
&Division {
|
||||
a: RegisterOrValue::Value(1337),
|
||||
b: RegisterOrValue::Register(r1),
|
||||
r: r1,
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![4, -334]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load() {
|
||||
assert!(matches!(
|
||||
Division::try_load(&[]),
|
||||
Err(ParametersError::UnexpectedCount {
|
||||
found: 0,
|
||||
expected: 3
|
||||
})
|
||||
));
|
||||
assert!(matches!(
|
||||
Division::try_load(&[
|
||||
RegisterOrValue::Value(42),
|
||||
RegisterOrValue::Value(23),
|
||||
RegisterOrValue::Value(1337),
|
||||
]),
|
||||
Err(ParametersError::UnexpectedType { index: 2 })
|
||||
));
|
||||
assert!(matches!(
|
||||
Division::try_load(&[
|
||||
RegisterOrValue::Value(42),
|
||||
RegisterOrValue::Value(23),
|
||||
RegisterOrValue::Register(RegisterIndex::R0),
|
||||
]),
|
||||
Ok(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print() {
|
||||
assert_eq!(
|
||||
"div 1337 r0 r1",
|
||||
format!(
|
||||
"{}",
|
||||
Division {
|
||||
a: RegisterOrValue::Value(1337),
|
||||
b: RegisterOrValue::Register(RegisterIndex::R0),
|
||||
r: RegisterIndex(1),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
72
echse/src/instructions/jmp_instruction.rs
Normal file
72
echse/src/instructions/jmp_instruction.rs
Normal file
|
@ -0,0 +1,72 @@
|
|||
use crate::instructions::{
|
||||
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction,
|
||||
};
|
||||
use crate::machine::{Machine, RegisterOrValue};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Jump {
|
||||
/// first in
|
||||
pub to: RegisterOrValue,
|
||||
}
|
||||
|
||||
impl Instruction for Jump {
|
||||
fn execute(&self, machine: &mut Machine) {
|
||||
machine.ip = self.to.read(machine) as usize;
|
||||
}
|
||||
}
|
||||
|
||||
impl InstructionName for Jump {
|
||||
const INSTRUCTION_NAME: &'static str = "jmp";
|
||||
}
|
||||
|
||||
impl Display for Jump {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(f, "{} {}", Self::INSTRUCTION_NAME, self.to)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryLoadInstruction for Jump {
|
||||
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
|
||||
check_param_count(params, 1)?;
|
||||
Ok(Rc::new(Jump { to: params[0] }))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::instructions::{Instruction, Jump};
|
||||
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
|
||||
|
||||
#[test]
|
||||
fn jump() {
|
||||
let r0 = RegisterIndex::new(0);
|
||||
let mut machine = MachineBuilder::new()
|
||||
.minimal_registers()
|
||||
.without_instructions()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(machine.registers.read_all(), vec![0]);
|
||||
assert_eq!(machine.ip, 0);
|
||||
Instruction::execute(
|
||||
&Jump {
|
||||
to: RegisterOrValue::Value(42),
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![0]);
|
||||
assert_eq!(machine.ip, 42);
|
||||
machine.registers[0].write(19);
|
||||
assert_eq!(machine.registers.read_all(), vec![19]);
|
||||
Instruction::execute(
|
||||
&Jump {
|
||||
to: RegisterOrValue::Register(r0),
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![19]);
|
||||
assert_eq!(machine.ip, 19);
|
||||
}
|
||||
}
|
80
echse/src/instructions/jze_instruction.rs
Normal file
80
echse/src/instructions/jze_instruction.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use crate::instructions::{
|
||||
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction,
|
||||
};
|
||||
use crate::machine::{Machine, RegisterOrValue};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct JumpIfZero {
|
||||
pub a: RegisterOrValue,
|
||||
pub to: RegisterOrValue,
|
||||
}
|
||||
|
||||
impl Instruction for JumpIfZero {
|
||||
fn execute(&self, machine: &mut Machine) {
|
||||
if self.a.read(machine) == 0 {
|
||||
machine.ip = self.to.read(machine) as usize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InstructionName for JumpIfZero {
|
||||
const INSTRUCTION_NAME: &'static str = "jze";
|
||||
}
|
||||
|
||||
impl Display for JumpIfZero {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(f, "{} {} {}", Self::INSTRUCTION_NAME, self.a, self.to)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryLoadInstruction for JumpIfZero {
|
||||
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
|
||||
check_param_count(params, 2)?;
|
||||
Ok(Rc::new(JumpIfZero {
|
||||
a: params[0],
|
||||
to: params[1],
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::instructions::{Instruction, JumpIfZero};
|
||||
use crate::machine::RegisterOrValue::Value;
|
||||
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
|
||||
|
||||
#[test]
|
||||
fn execute() {
|
||||
let r0 = RegisterIndex::new(0);
|
||||
let mut machine = MachineBuilder::new()
|
||||
.minimal_registers()
|
||||
.without_instructions()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(machine.registers.read_all(), vec![0]);
|
||||
assert_eq!(machine.ip, 0);
|
||||
Instruction::execute(
|
||||
&JumpIfZero {
|
||||
a: Value(0),
|
||||
to: Value(42),
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![0]);
|
||||
assert_eq!(machine.ip, 42);
|
||||
machine.registers[0].write(19);
|
||||
assert_eq!(machine.registers.read_all(), vec![19]);
|
||||
Instruction::execute(
|
||||
&JumpIfZero {
|
||||
a: RegisterOrValue::Register(r0),
|
||||
to: RegisterOrValue::Register(r0),
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![19]);
|
||||
assert_eq!(machine.ip, 42);
|
||||
}
|
||||
}
|
29
echse/src/instructions/mod.rs
Normal file
29
echse/src/instructions/mod.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
mod add_instruction;
|
||||
mod collection;
|
||||
mod div_instruction;
|
||||
mod jmp_instruction;
|
||||
mod jze_instruction;
|
||||
mod mod_instruction;
|
||||
mod nop_instruction;
|
||||
mod put_instruction;
|
||||
mod sub_instruction;
|
||||
mod teq_instruction;
|
||||
mod tlt_instruction;
|
||||
mod traits;
|
||||
mod try_load;
|
||||
mod tze_instruction;
|
||||
|
||||
pub use add_instruction::*;
|
||||
pub use collection::*;
|
||||
pub use div_instruction::*;
|
||||
pub use jmp_instruction::*;
|
||||
pub use jze_instruction::*;
|
||||
pub use mod_instruction::*;
|
||||
pub use nop_instruction::*;
|
||||
pub use put_instruction::*;
|
||||
pub use sub_instruction::*;
|
||||
pub use teq_instruction::*;
|
||||
pub use tlt_instruction::*;
|
||||
pub use traits::*;
|
||||
pub use try_load::*;
|
||||
pub use tze_instruction::*;
|
135
echse/src/instructions/mod_instruction.rs
Normal file
135
echse/src/instructions/mod_instruction.rs
Normal file
|
@ -0,0 +1,135 @@
|
|||
use crate::instructions::{
|
||||
try_load_binary_op, Instruction, InstructionName, ParametersError, TryLoadInstruction,
|
||||
};
|
||||
use crate::machine::{Machine, RegisterIndex, RegisterOrValue};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Modulus {
|
||||
/// first input
|
||||
pub a: RegisterOrValue,
|
||||
/// second input
|
||||
pub b: RegisterOrValue,
|
||||
/// result register
|
||||
pub r: RegisterIndex,
|
||||
}
|
||||
|
||||
impl Instruction for Modulus {
|
||||
fn execute(&self, machine: &mut Machine) {
|
||||
let value = self.a.read(machine) % self.b.read(machine);
|
||||
self.r.write(machine, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl InstructionName for Modulus {
|
||||
const INSTRUCTION_NAME: &'static str = "mod";
|
||||
}
|
||||
|
||||
impl Display for Modulus {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
"{} {} {} {}",
|
||||
Self::INSTRUCTION_NAME,
|
||||
self.a,
|
||||
self.b,
|
||||
self.r
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryLoadInstruction for Modulus {
|
||||
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
|
||||
let (a, b, r) = try_load_binary_op(params)?;
|
||||
Ok(Rc::new(Self { a, b, r }))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::instructions::{Instruction, Modulus, ParametersError, TryLoadInstruction};
|
||||
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
|
||||
|
||||
#[test]
|
||||
fn execute() {
|
||||
let r0 = RegisterIndex::new(0);
|
||||
let r1 = RegisterIndex::new(1);
|
||||
let mut machine = MachineBuilder::new()
|
||||
.with_registers(2)
|
||||
.without_instructions()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(machine.registers.read_all(), vec![0, 0]);
|
||||
Instruction::execute(
|
||||
&Modulus {
|
||||
a: RegisterOrValue::Value(42),
|
||||
b: RegisterOrValue::Value(23),
|
||||
r: r0,
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![19, 0]);
|
||||
Instruction::execute(
|
||||
&Modulus {
|
||||
a: RegisterOrValue::Register(r0),
|
||||
b: RegisterOrValue::Value(-23),
|
||||
r: r1,
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![19, 19]);
|
||||
Instruction::execute(
|
||||
&Modulus {
|
||||
a: RegisterOrValue::Value(1337),
|
||||
b: RegisterOrValue::Register(r1),
|
||||
r: r1,
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![19, 7]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load() {
|
||||
assert!(matches!(
|
||||
Modulus::try_load(&[]),
|
||||
Err(ParametersError::UnexpectedCount {
|
||||
found: 0,
|
||||
expected: 3
|
||||
})
|
||||
));
|
||||
assert!(matches!(
|
||||
Modulus::try_load(&[
|
||||
RegisterOrValue::Value(42),
|
||||
RegisterOrValue::Value(23),
|
||||
RegisterOrValue::Value(1337),
|
||||
]),
|
||||
Err(ParametersError::UnexpectedType { index: 2 })
|
||||
));
|
||||
assert!(matches!(
|
||||
Modulus::try_load(&[
|
||||
RegisterOrValue::Value(42),
|
||||
RegisterOrValue::Value(23),
|
||||
RegisterOrValue::Register(RegisterIndex::R0),
|
||||
]),
|
||||
Ok(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print() {
|
||||
assert_eq!(
|
||||
"mod 1337 r0 r1",
|
||||
format!(
|
||||
"{}",
|
||||
Modulus {
|
||||
a: RegisterOrValue::Value(1337),
|
||||
b: RegisterOrValue::Register(RegisterIndex::R0),
|
||||
r: RegisterIndex(1),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
32
echse/src/instructions/nop_instruction.rs
Normal file
32
echse/src/instructions/nop_instruction.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use crate::instructions::{
|
||||
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction,
|
||||
};
|
||||
use crate::machine::{Machine, RegisterOrValue};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NoOperation {}
|
||||
|
||||
impl Instruction for NoOperation {
|
||||
fn execute(&self, _: &mut Machine) {
|
||||
// intentionally left empty as this is the no operation instruction
|
||||
}
|
||||
}
|
||||
|
||||
impl InstructionName for NoOperation {
|
||||
const INSTRUCTION_NAME: &'static str = "nop";
|
||||
}
|
||||
|
||||
impl TryLoadInstruction for NoOperation {
|
||||
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
|
||||
check_param_count(params, 0)?;
|
||||
Ok(Rc::new(NoOperation {}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for NoOperation {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
f.write_str(Self::INSTRUCTION_NAME)
|
||||
}
|
||||
}
|
79
echse/src/instructions/put_instruction.rs
Normal file
79
echse/src/instructions/put_instruction.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
use crate::instructions::{
|
||||
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction,
|
||||
};
|
||||
use crate::machine::{Machine, RegisterIndex, RegisterOrValue};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Put {
|
||||
/// input
|
||||
pub a: RegisterOrValue,
|
||||
/// result register
|
||||
pub r: RegisterIndex,
|
||||
}
|
||||
|
||||
impl Instruction for Put {
|
||||
fn execute(&self, machine: &mut Machine) {
|
||||
let value = self.a.read(machine);
|
||||
self.r.write(machine, value);
|
||||
}
|
||||
}
|
||||
|
||||
impl InstructionName for Put {
|
||||
const INSTRUCTION_NAME: &'static str = "put";
|
||||
}
|
||||
|
||||
impl Display for Put {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(f, "{} {} {}", Self::INSTRUCTION_NAME, self.a, self.r)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryLoadInstruction for Put {
|
||||
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
|
||||
check_param_count(params, 2)?;
|
||||
|
||||
let r = match params[1] {
|
||||
RegisterOrValue::Register(r) => r,
|
||||
RegisterOrValue::Value(_) => return Err(ParametersError::UnexpectedType { index: 2 }),
|
||||
};
|
||||
|
||||
Ok(Rc::new(Self { a: params[0], r }))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::instructions::{Instruction, Put};
|
||||
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
|
||||
|
||||
#[test]
|
||||
fn put() {
|
||||
let r0 = RegisterIndex::new(0);
|
||||
let r1 = RegisterIndex::new(1);
|
||||
let mut machine = MachineBuilder::new()
|
||||
.with_registers(2)
|
||||
.without_instructions()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(machine.registers.read_all(), vec![0, 0]);
|
||||
Instruction::execute(
|
||||
&Put {
|
||||
a: RegisterOrValue::Value(42),
|
||||
r: r0,
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![42, 0]);
|
||||
Instruction::execute(
|
||||
&Put {
|
||||
a: RegisterOrValue::Register(r0),
|
||||
r: r1,
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![42, 42]);
|
||||
}
|
||||
}
|
135
echse/src/instructions/sub_instruction.rs
Normal file
135
echse/src/instructions/sub_instruction.rs
Normal file
|
@ -0,0 +1,135 @@
|
|||
use crate::instructions::{
|
||||
try_load_binary_op, Instruction, InstructionName, ParametersError, TryLoadInstruction,
|
||||
};
|
||||
use crate::machine::{Machine, RegisterIndex, RegisterOrValue};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Subtraction {
|
||||
/// first input
|
||||
pub a: RegisterOrValue,
|
||||
/// second input
|
||||
pub b: RegisterOrValue,
|
||||
/// result register
|
||||
pub r: RegisterIndex,
|
||||
}
|
||||
|
||||
impl Instruction for Subtraction {
|
||||
fn execute(&self, machine: &mut Machine) {
|
||||
let value = self.a.read(machine) - self.b.read(machine);
|
||||
self.r.write(machine, value);
|
||||
}
|
||||
}
|
||||
|
||||
impl InstructionName for Subtraction {
|
||||
const INSTRUCTION_NAME: &'static str = "sub";
|
||||
}
|
||||
|
||||
impl Display for Subtraction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
"{} {} {} {}",
|
||||
Self::INSTRUCTION_NAME,
|
||||
self.a,
|
||||
self.b,
|
||||
self.r
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryLoadInstruction for Subtraction {
|
||||
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
|
||||
let (a, b, r) = try_load_binary_op(params)?;
|
||||
Ok(Rc::new(Self { a, b, r }))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::instructions::{Instruction, ParametersError, Subtraction, TryLoadInstruction};
|
||||
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
|
||||
|
||||
#[test]
|
||||
fn execute() {
|
||||
let r0 = RegisterIndex::new(0);
|
||||
let r1 = RegisterIndex::new(1);
|
||||
let mut machine = MachineBuilder::new()
|
||||
.with_registers(2)
|
||||
.without_instructions()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(machine.registers.read_all(), vec![0, 0]);
|
||||
Instruction::execute(
|
||||
&Subtraction {
|
||||
a: RegisterOrValue::Value(42),
|
||||
b: RegisterOrValue::Value(23),
|
||||
r: r0,
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![19, 0]);
|
||||
Instruction::execute(
|
||||
&Subtraction {
|
||||
a: RegisterOrValue::Register(r0),
|
||||
b: RegisterOrValue::Value(-23),
|
||||
r: r1,
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![19, 42]);
|
||||
Instruction::execute(
|
||||
&Subtraction {
|
||||
a: RegisterOrValue::Value(1337),
|
||||
b: RegisterOrValue::Register(r1),
|
||||
r: r1,
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers.read_all(), vec![19, 1295]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load() {
|
||||
assert!(matches!(
|
||||
Subtraction::try_load(&[]),
|
||||
Err(ParametersError::UnexpectedCount {
|
||||
found: 0,
|
||||
expected: 3
|
||||
})
|
||||
));
|
||||
assert!(matches!(
|
||||
Subtraction::try_load(&[
|
||||
RegisterOrValue::Value(42),
|
||||
RegisterOrValue::Value(23),
|
||||
RegisterOrValue::Value(1337),
|
||||
]),
|
||||
Err(ParametersError::UnexpectedType { index: 2 })
|
||||
));
|
||||
assert!(matches!(
|
||||
Subtraction::try_load(&[
|
||||
RegisterOrValue::Value(42),
|
||||
RegisterOrValue::Value(23),
|
||||
RegisterOrValue::Register(RegisterIndex::R0),
|
||||
]),
|
||||
Ok(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print() {
|
||||
assert_eq!(
|
||||
"sub 1337 r0 r1",
|
||||
format!(
|
||||
"{}",
|
||||
Subtraction {
|
||||
a: RegisterOrValue::Value(1337),
|
||||
b: RegisterOrValue::Register(RegisterIndex::R0),
|
||||
r: RegisterIndex(1),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
107
echse/src/instructions/teq_instruction.rs
Normal file
107
echse/src/instructions/teq_instruction.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
use crate::instructions::{
|
||||
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction,
|
||||
};
|
||||
use crate::machine::{Machine, RegisterOrValue};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestEqual {
|
||||
pub a: RegisterOrValue,
|
||||
pub b: RegisterOrValue,
|
||||
}
|
||||
|
||||
impl Instruction for TestEqual {
|
||||
fn execute(&self, machine: &mut Machine) {
|
||||
let result = self.a.read(machine) == self.b.read(machine);
|
||||
machine.registers[0].write(if result { 1 } else { 0 });
|
||||
}
|
||||
}
|
||||
|
||||
impl InstructionName for TestEqual {
|
||||
const INSTRUCTION_NAME: &'static str = "teq";
|
||||
}
|
||||
|
||||
impl Display for TestEqual {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(f, "{} {} {}", Self::INSTRUCTION_NAME, self.a, self.b)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryLoadInstruction for TestEqual {
|
||||
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
|
||||
check_param_count(params, 2)?;
|
||||
Ok(Rc::new(Self {
|
||||
a: params[0],
|
||||
b: params[1],
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::instructions::{Instruction, ParametersError, TestEqual, TryLoadInstruction};
|
||||
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
|
||||
|
||||
#[test]
|
||||
fn execute() {
|
||||
let r0 = RegisterIndex::new(0);
|
||||
let mut machine = MachineBuilder::new()
|
||||
.minimal_registers()
|
||||
.without_instructions()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(machine.registers[0].read(), 0);
|
||||
Instruction::execute(
|
||||
&TestEqual {
|
||||
a: RegisterOrValue::Value(42),
|
||||
b: RegisterOrValue::Value(23),
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers[0].read(), 0);
|
||||
|
||||
machine.registers[0].write(-23);
|
||||
Instruction::execute(
|
||||
&TestEqual {
|
||||
a: RegisterOrValue::Register(r0),
|
||||
b: RegisterOrValue::Value(-23),
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers[0].read(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load() {
|
||||
assert!(matches!(
|
||||
TestEqual::try_load(&[]),
|
||||
Err(ParametersError::UnexpectedCount {
|
||||
found: 0,
|
||||
expected: 2
|
||||
})
|
||||
));
|
||||
assert!(matches!(
|
||||
TestEqual::try_load(&[
|
||||
RegisterOrValue::Value(42),
|
||||
RegisterOrValue::Register(RegisterIndex::R0),
|
||||
]),
|
||||
Ok(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print() {
|
||||
assert_eq!(
|
||||
"teq 1337 r0",
|
||||
format!(
|
||||
"{}",
|
||||
TestEqual {
|
||||
a: RegisterOrValue::Value(1337),
|
||||
b: RegisterOrValue::Register(RegisterIndex::R0),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
104
echse/src/instructions/tlt_instruction.rs
Normal file
104
echse/src/instructions/tlt_instruction.rs
Normal file
|
@ -0,0 +1,104 @@
|
|||
use crate::instructions::{
|
||||
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction,
|
||||
};
|
||||
use crate::machine::{Machine, RegisterOrValue};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestLessThan {
|
||||
pub a: RegisterOrValue,
|
||||
pub b: RegisterOrValue,
|
||||
}
|
||||
|
||||
impl Instruction for TestLessThan {
|
||||
fn execute(&self, machine: &mut Machine) {
|
||||
let result = self.a.read(machine) < self.b.read(machine);
|
||||
machine.registers[0].write(if result { 1 } else { 0 });
|
||||
}
|
||||
}
|
||||
|
||||
impl InstructionName for TestLessThan {
|
||||
const INSTRUCTION_NAME: &'static str = "tlt";
|
||||
}
|
||||
|
||||
impl Display for TestLessThan {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(f, "{} {} {}", Self::INSTRUCTION_NAME, self.a, self.b)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryLoadInstruction for TestLessThan {
|
||||
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
|
||||
check_param_count(params, 2)?;
|
||||
Ok(Rc::new(Self {
|
||||
a: params[0],
|
||||
b: params[1],
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::instructions::{Instruction, ParametersError, TestLessThan, TryLoadInstruction};
|
||||
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
|
||||
|
||||
#[test]
|
||||
fn execute() {
|
||||
let r0 = RegisterIndex::new(0);
|
||||
let mut machine = MachineBuilder::new()
|
||||
.minimal_registers()
|
||||
.without_instructions()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(machine.registers[0].read(), 0);
|
||||
Instruction::execute(
|
||||
&TestLessThan {
|
||||
a: RegisterOrValue::Value(42),
|
||||
b: RegisterOrValue::Value(23),
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers[0].read(), 0);
|
||||
|
||||
machine.registers[0].write(-24);
|
||||
Instruction::execute(
|
||||
&TestLessThan {
|
||||
a: RegisterOrValue::Register(r0),
|
||||
b: RegisterOrValue::Value(-23),
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(machine.registers[0].read(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load() {
|
||||
assert!(matches!(
|
||||
TestLessThan::try_load(&[]),
|
||||
Err(ParametersError::UnexpectedCount {
|
||||
found: 0,
|
||||
expected: 2
|
||||
})
|
||||
));
|
||||
assert!(matches!(
|
||||
TestLessThan::try_load(&[RegisterOrValue::Value(42), RegisterOrValue::Value(23),]),
|
||||
Ok(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print() {
|
||||
assert_eq!(
|
||||
"tlt 1337 r0",
|
||||
format!(
|
||||
"{}",
|
||||
TestLessThan {
|
||||
a: RegisterOrValue::Value(1337),
|
||||
b: RegisterOrValue::Register(RegisterIndex::R0),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
11
echse/src/instructions/traits.rs
Normal file
11
echse/src/instructions/traits.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use crate::machine::Machine;
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
pub trait Instruction: Debug + Display {
|
||||
// TODO: this needs to be able to return errors, e.g. for division by zero
|
||||
fn execute(&self, machine: &mut Machine);
|
||||
}
|
||||
|
||||
pub trait InstructionName {
|
||||
const INSTRUCTION_NAME: &'static str;
|
||||
}
|
39
echse/src/instructions/try_load.rs
Normal file
39
echse/src/instructions/try_load.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use crate::machine::{RegisterIndex, RegisterOrValue};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub(crate) trait TryLoadInstruction {
|
||||
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ParametersError {
|
||||
UnexpectedCount { expected: usize, found: usize },
|
||||
UnexpectedType { index: usize },
|
||||
}
|
||||
|
||||
pub(crate) fn check_param_count(
|
||||
params: &[RegisterOrValue],
|
||||
expected: usize,
|
||||
) -> Result<(), ParametersError> {
|
||||
if params.len() != expected {
|
||||
Err(ParametersError::UnexpectedCount {
|
||||
expected,
|
||||
found: params.len(),
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_load_binary_op(
|
||||
params: &[RegisterOrValue],
|
||||
) -> Result<(RegisterOrValue, RegisterOrValue, RegisterIndex), ParametersError> {
|
||||
check_param_count(params, 3)?;
|
||||
|
||||
let r = match params[2] {
|
||||
RegisterOrValue::Register(r) => r,
|
||||
RegisterOrValue::Value(_) => return Err(ParametersError::UnexpectedType { index: 2 }),
|
||||
};
|
||||
|
||||
Ok((params[0], params[1], r))
|
||||
}
|
62
echse/src/instructions/tze_instruction.rs
Normal file
62
echse/src/instructions/tze_instruction.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
use crate::instructions::{
|
||||
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction,
|
||||
};
|
||||
use crate::machine::{Machine, RegisterOrValue};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestZero {
|
||||
a: RegisterOrValue,
|
||||
}
|
||||
|
||||
impl Instruction for TestZero {
|
||||
fn execute(&self, machine: &mut Machine) {
|
||||
let result = self.a.read(machine) == 0;
|
||||
machine.registers[0].write(if result { 1 } else { 0 });
|
||||
}
|
||||
}
|
||||
|
||||
impl InstructionName for TestZero {
|
||||
const INSTRUCTION_NAME: &'static str = "tze";
|
||||
}
|
||||
|
||||
impl Display for TestZero {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(f, "{} {}", Self::INSTRUCTION_NAME, self.a)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryLoadInstruction for TestZero {
|
||||
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
|
||||
check_param_count(params, 1)?;
|
||||
Ok(Rc::new(TestZero { a: params[0] }))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::instructions::{Instruction, TestZero};
|
||||
use crate::machine::RegisterOrValue::{Register, Value};
|
||||
use crate::machine::{MachineBuilder, RegisterIndex};
|
||||
|
||||
#[test]
|
||||
fn execute() {
|
||||
let mut machine = MachineBuilder::new()
|
||||
.minimal_registers()
|
||||
.without_instructions()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(0, machine.registers[0].read());
|
||||
Instruction::execute(&TestZero { a: Value(42) }, &mut machine);
|
||||
assert_eq!(0, machine.registers[0].read());
|
||||
Instruction::execute(
|
||||
&TestZero {
|
||||
a: Register(RegisterIndex::R0),
|
||||
},
|
||||
&mut machine,
|
||||
);
|
||||
assert_eq!(1, machine.registers[0].read());
|
||||
}
|
||||
}
|
7
echse/src/lib.rs
Normal file
7
echse/src/lib.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
pub mod instructions;
|
||||
pub mod machine;
|
||||
pub mod parser;
|
||||
|
||||
pub(crate) mod sealed {
|
||||
pub trait Sealed {}
|
||||
}
|
159
echse/src/machine/machine_struct.rs
Normal file
159
echse/src/machine/machine_struct.rs
Normal file
|
@ -0,0 +1,159 @@
|
|||
use crate::instructions::Instructions;
|
||||
use crate::machine::Registers;
|
||||
use crate::sealed::Sealed;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Machine {
|
||||
pub ip: usize,
|
||||
pub registers: Registers,
|
||||
pub instructions: Instructions,
|
||||
}
|
||||
|
||||
impl Machine {
|
||||
pub fn step(&mut self) -> bool {
|
||||
if self.ip >= self.instructions.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let instruction = &self.instructions[self.ip].clone();
|
||||
instruction.execute(self);
|
||||
self.ip += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Machine {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
f.write_str("- exe-cute table -\n")?;
|
||||
f.write_str("registers")?;
|
||||
f.write_fmt(format_args!("ip: {}\n", self.ip))?;
|
||||
f.write_fmt(format_args!("{}", self.registers))?;
|
||||
f.write_fmt(format_args!("instructions\n{}", self.instructions))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MachineBuilderState: Sealed {}
|
||||
pub struct MachineBuilder<T: MachineBuilderState> {
|
||||
state: T,
|
||||
}
|
||||
|
||||
impl MachineBuilder<SetRegisters> {
|
||||
pub fn new() -> MachineBuilder<SetRegisters> {
|
||||
MachineBuilder {
|
||||
state: SetRegisters {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MachineBuilder<SetRegisters> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SetRegisters {}
|
||||
impl Sealed for SetRegisters {}
|
||||
impl MachineBuilderState for SetRegisters {}
|
||||
impl MachineBuilder<SetRegisters> {
|
||||
/// Sets the amount of registers to the specified amount, but at least 1.
|
||||
pub fn with_registers(self, registers: usize) -> MachineBuilder<LoadInstructions> {
|
||||
MachineBuilder {
|
||||
state: LoadInstructions {
|
||||
registers: Registers::with_count(usize::max(1, registers)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the amount of registers to 1.
|
||||
pub fn minimal_registers(self) -> MachineBuilder<LoadInstructions> {
|
||||
self.with_registers(1)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LoadInstructions {
|
||||
registers: Registers,
|
||||
}
|
||||
impl MachineBuilderState for LoadInstructions {}
|
||||
impl Sealed for LoadInstructions {}
|
||||
impl MachineBuilder<LoadInstructions> {
|
||||
pub fn with_instructions(self, instructions: Instructions) -> MachineBuilder<Done> {
|
||||
MachineBuilder {
|
||||
state: Done {
|
||||
registers: self.state.registers,
|
||||
instructions,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn without_instructions(self) -> MachineBuilder<Done> {
|
||||
self.with_instructions(Instructions::empty())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Done {
|
||||
registers: Registers,
|
||||
instructions: Instructions,
|
||||
}
|
||||
impl MachineBuilderState for Done {}
|
||||
impl Sealed for Done {}
|
||||
impl MachineBuilder<Done> {
|
||||
pub fn build(self) -> Result<Machine, MachineBuilderError> {
|
||||
Ok(Machine {
|
||||
ip: 0,
|
||||
registers: self.state.registers,
|
||||
instructions: self.state.instructions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MachineBuilderError {
|
||||
NoRegisters,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::instructions::{Addition, Instructions};
|
||||
use crate::instructions::{Instruction, Put};
|
||||
use crate::machine::register::{RegisterIndex, RegisterOrValue};
|
||||
use crate::machine::MachineBuilder;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let reg_a = RegisterIndex::R0;
|
||||
let instructions: Vec<Rc<dyn Instruction>> = vec![
|
||||
Rc::new(Put {
|
||||
a: RegisterOrValue::Value(42),
|
||||
r: reg_a,
|
||||
}),
|
||||
Rc::new(Addition {
|
||||
a: RegisterOrValue::Register(reg_a),
|
||||
b: RegisterOrValue::Value(23),
|
||||
r: reg_a,
|
||||
}),
|
||||
];
|
||||
|
||||
let mut machine = MachineBuilder::new()
|
||||
.minimal_registers()
|
||||
.with_instructions(Instructions::from(instructions))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(machine.ip, 0);
|
||||
assert_eq!(reg_a.read(&machine), 0);
|
||||
|
||||
assert!(machine.step());
|
||||
assert_eq!(machine.ip, 1);
|
||||
assert_eq!(reg_a.read(&machine), 42);
|
||||
|
||||
assert!(machine.step());
|
||||
assert_eq!(machine.ip, 2);
|
||||
assert_eq!(reg_a.read(&machine), 65);
|
||||
|
||||
assert!(!machine.step());
|
||||
assert!(!machine.step());
|
||||
}
|
||||
}
|
5
echse/src/machine/mod.rs
Normal file
5
echse/src/machine/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod machine_struct;
|
||||
mod register;
|
||||
|
||||
pub use machine_struct::*;
|
||||
pub use register::*;
|
130
echse/src/machine/register.rs
Normal file
130
echse/src/machine/register.rs
Normal file
|
@ -0,0 +1,130 @@
|
|||
use crate::machine::Machine;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::Display;
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Register<T: Copy + Sized + Default> {
|
||||
value: T,
|
||||
}
|
||||
|
||||
impl<T: Copy + Sized + Default> Register<T> {
|
||||
pub fn read(&self) -> T {
|
||||
self.value
|
||||
}
|
||||
|
||||
pub fn write(&mut self, value: T) {
|
||||
self.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
pub type GpRegister = Register<isize>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Registers {
|
||||
rs: Vec<GpRegister>,
|
||||
}
|
||||
|
||||
impl Registers {
|
||||
pub fn new(registers: Vec<GpRegister>) -> Self {
|
||||
assert!(!registers.is_empty());
|
||||
Self { rs: registers }
|
||||
}
|
||||
|
||||
pub fn with_count(count: usize) -> Self {
|
||||
assert!(count > 0);
|
||||
Self::new(vec![GpRegister::default(); count])
|
||||
}
|
||||
|
||||
pub fn read_all(&self) -> Vec<isize> {
|
||||
self.iter().map(|r| r.read()).collect()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &GpRegister> {
|
||||
self.rs.iter()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.rs.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.rs.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Registers {
|
||||
type Output = GpRegister;
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.rs[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<usize> for Registers {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
&mut self.rs[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Registers {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
for (index, register) in self.iter().enumerate() {
|
||||
f.write_fmt(format_args!("r{index}: {}\n", register.read()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub struct RegisterIndex(pub usize);
|
||||
|
||||
impl RegisterIndex {
|
||||
pub const R0: RegisterIndex = RegisterIndex(0);
|
||||
|
||||
pub fn new_checked(index: usize, machine: &Machine) -> Self {
|
||||
assert!(machine.registers.rs.len() > index);
|
||||
Self::new(index)
|
||||
}
|
||||
|
||||
pub fn new(index: usize) -> Self {
|
||||
Self(index)
|
||||
}
|
||||
|
||||
pub fn read(&self, machine: &Machine) -> isize {
|
||||
machine.registers.rs[self.0].read()
|
||||
}
|
||||
|
||||
pub fn write(&self, machine: &mut Machine, value: isize) {
|
||||
machine.registers.rs[self.0].write(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RegisterIndex {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
f.write_fmt(format_args!("r{}", self.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum RegisterOrValue {
|
||||
Register(RegisterIndex),
|
||||
Value(isize),
|
||||
}
|
||||
|
||||
impl RegisterOrValue {
|
||||
pub fn read(&self, machine: &Machine) -> isize {
|
||||
match self {
|
||||
RegisterOrValue::Register(r) => r.read(machine),
|
||||
RegisterOrValue::Value(v) => *v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RegisterOrValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
match self {
|
||||
RegisterOrValue::Register(r) => Display::fmt(&r, f),
|
||||
RegisterOrValue::Value(v) => Display::fmt(&v, f),
|
||||
}
|
||||
}
|
||||
}
|
266
echse/src/parser/ei.rs
Normal file
266
echse/src/parser/ei.rs
Normal file
|
@ -0,0 +1,266 @@
|
|||
use crate::instructions::{
|
||||
Addition, Division, Instruction, InstructionName, Instructions, Jump, JumpIfZero, Modulus,
|
||||
NoOperation, ParametersError, Put, Subtraction, TestEqual, TestLessThan, TestZero,
|
||||
TryLoadInstruction,
|
||||
};
|
||||
use crate::parser;
|
||||
use crate::parser::ei_ast::{Line};
|
||||
use std::io::Write;
|
||||
use std::rc::Rc;
|
||||
use crate::parser::ParseError;
|
||||
|
||||
pub struct EiParser {
|
||||
file_name: String,
|
||||
file_content: String,
|
||||
}
|
||||
|
||||
impl EiParser {
|
||||
pub fn from_file(file_name: &str) -> std::io::Result<EiParser> {
|
||||
Ok(Self {
|
||||
file_name: file_name.to_string(),
|
||||
file_content: std::fs::read_to_string(file_name)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_string(file_content: &str) -> Self {
|
||||
Self {
|
||||
file_name: "input".to_string(),
|
||||
file_content: file_content.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(&self) -> Result<Instructions, EiParserError> {
|
||||
let lines = match parser::ei_ast::parse_program(self.file_content.as_str()) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return Err(EiParserError::ParseError(e)),
|
||||
};
|
||||
|
||||
Self::try_load_instructions(lines).map_err(EiParserError::InstructionLoadError)
|
||||
}
|
||||
|
||||
pub fn try_load_instructions(
|
||||
lines: impl IntoIterator<Item = Line>,
|
||||
) -> Result<Instructions, TryLoadError> {
|
||||
let lines = lines.into_iter();
|
||||
let mut vec = Vec::with_capacity(lines.size_hint().0);
|
||||
|
||||
for (index, line) in lines.enumerate() {
|
||||
let instruction = match Self::try_load_instruction(&line) {
|
||||
Ok(instruction) => instruction,
|
||||
Err(reason) => {
|
||||
return Err(TryLoadError {
|
||||
line,
|
||||
index,
|
||||
reason,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
vec.push(instruction);
|
||||
}
|
||||
|
||||
Ok(Instructions::from(vec))
|
||||
}
|
||||
|
||||
fn try_load_instruction(line: &Line) -> Result<Rc<dyn Instruction>, TryLoadErrorReason> {
|
||||
let params = line
|
||||
.parameters
|
||||
.iter()
|
||||
.map(move |(value, _)| *value)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(match &*line.instruction_name {
|
||||
Addition::INSTRUCTION_NAME => Addition::try_load(¶ms)?,
|
||||
Subtraction::INSTRUCTION_NAME => Subtraction::try_load(¶ms)?,
|
||||
Modulus::INSTRUCTION_NAME => Modulus::try_load(¶ms)?,
|
||||
Division::INSTRUCTION_NAME => Division::try_load(¶ms)?,
|
||||
Jump::INSTRUCTION_NAME => Jump::try_load(¶ms)?,
|
||||
NoOperation::INSTRUCTION_NAME => NoOperation::try_load(¶ms)?,
|
||||
TestZero::INSTRUCTION_NAME => TestZero::try_load(¶ms)?,
|
||||
Put::INSTRUCTION_NAME => Put::try_load(¶ms)?,
|
||||
TestEqual::INSTRUCTION_NAME => TestEqual::try_load(¶ms)?,
|
||||
TestLessThan::INSTRUCTION_NAME => TestLessThan::try_load(¶ms)?,
|
||||
JumpIfZero::INSTRUCTION_NAME => JumpIfZero::try_load(¶ms)?,
|
||||
illegal => return Err(TryLoadErrorReason::IllegalInstruction(illegal.to_string())),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "error-report")]
|
||||
impl EiParser {
|
||||
pub fn write_error_report<W: Write>(
|
||||
&self,
|
||||
ei_parser_error: EiParserError,
|
||||
w: &mut W,
|
||||
) -> std::io::Result<()>
|
||||
where
|
||||
for<'a> &'a mut W: Write,
|
||||
{
|
||||
match ei_parser_error {
|
||||
EiParserError::ParseError(es) => {
|
||||
for e in es {
|
||||
self.write_parse_error_report(e, &mut *w)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
EiParserError::InstructionLoadError(e) => self.write_load_error_report(e, &mut *w),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_load_error_report<W: std::io::Write>(
|
||||
&self,
|
||||
e: TryLoadError,
|
||||
w: &mut W,
|
||||
) -> std::io::Result<()> {
|
||||
use ariadne::{Color, Label, Report, ReportKind, Source};
|
||||
|
||||
let report = Report::build(ReportKind::Error, (&self.file_name, e.line.span.clone()))
|
||||
.with_message("Loading of instructions failed")
|
||||
.with_config(Self::get_ariadne_config());
|
||||
|
||||
let report = match e.reason {
|
||||
TryLoadErrorReason::IllegalInstruction(_) => {
|
||||
let label = Label::new((&self.file_name, e.line.instruction_name_span))
|
||||
.with_color(Color::Red)
|
||||
.with_message("illegal instruction");
|
||||
report
|
||||
.with_label(label)
|
||||
.with_note("instruction names are case sensitive")
|
||||
}
|
||||
TryLoadErrorReason::InvalidParameters(param_err) => match param_err {
|
||||
ParametersError::UnexpectedCount { found, expected } => {
|
||||
let label = if found > expected {
|
||||
let extra_args = &e.line.parameters[expected..found];
|
||||
let span =
|
||||
extra_args.first().unwrap().1.start..extra_args.last().unwrap().1.end;
|
||||
|
||||
Label::new((&self.file_name, span))
|
||||
.with_color(Color::Red)
|
||||
.with_message("too many parameters")
|
||||
} else {
|
||||
Label::new((&self.file_name, e.line.parameters_span.clone()))
|
||||
.with_color(Color::Red)
|
||||
.with_message("not enough parameters")
|
||||
};
|
||||
report.with_label(label).with_note(format!(
|
||||
"the instruction expects {expected} parameters, but {found} were provided."
|
||||
))
|
||||
}
|
||||
ParametersError::UnexpectedType { index } => {
|
||||
let label = Label::new((&self.file_name, e.line.parameters[index].1.clone()))
|
||||
.with_color(Color::Red)
|
||||
.with_message("unexpected type of parameter");
|
||||
report.with_label(label).with_help(
|
||||
"check the parameter order and which ones can values and/or registers",
|
||||
)
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
report
|
||||
.finish()
|
||||
.write((&self.file_name, Source::from(&self.file_content)), w)
|
||||
}
|
||||
|
||||
#[cfg(feature = "error-report")]
|
||||
pub fn write_parse_error_report<W: std::io::Write>(
|
||||
&self,
|
||||
e: ParseError,
|
||||
w: W,
|
||||
) -> std::io::Result<()> {
|
||||
use ariadne::{Color, Fmt, Label, Report, ReportKind, Source};
|
||||
|
||||
let report = Report::build(ReportKind::Error, (&self.file_name, e.span()))
|
||||
.with_config(Self::get_ariadne_config());
|
||||
|
||||
let report = match e.reason() {
|
||||
chumsky::error::SimpleReason::Unclosed { span, delimiter } => report
|
||||
.with_message(format!(
|
||||
"Unclosed delimiter {}",
|
||||
delimiter.fg(Color::Yellow)
|
||||
))
|
||||
.with_label(
|
||||
Label::new((&self.file_name, span.clone()))
|
||||
.with_message(format!(
|
||||
"Unclosed delimiter {}",
|
||||
delimiter.fg(Color::Yellow)
|
||||
))
|
||||
.with_color(Color::Yellow),
|
||||
)
|
||||
.with_label(
|
||||
Label::new((&self.file_name, e.span()))
|
||||
.with_message(format!(
|
||||
"Must be closed before this {:?}",
|
||||
e.found().unwrap_or(&'\0').fg(Color::Red)
|
||||
))
|
||||
.with_color(Color::Red),
|
||||
),
|
||||
chumsky::error::SimpleReason::Unexpected => report
|
||||
.with_message(format!(
|
||||
"{}, expected {}",
|
||||
if e.found().is_some() {
|
||||
"Unexpected token in input"
|
||||
} else {
|
||||
"Unexpected end of input"
|
||||
},
|
||||
if e.expected().len() == 0 {
|
||||
"something else".to_string()
|
||||
} else {
|
||||
e.expected()
|
||||
.map(|expected| match expected {
|
||||
Some(expected) => expected.to_string(),
|
||||
None => "end of input".to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
))
|
||||
.with_label(
|
||||
Label::new((&self.file_name, e.span()))
|
||||
.with_message(format!(
|
||||
"Unexpected token {}",
|
||||
e.found().unwrap_or(&'\0').fg(Color::Red)
|
||||
))
|
||||
.with_color(Color::Red),
|
||||
),
|
||||
chumsky::error::SimpleReason::Custom(msg) => report.with_message(msg).with_label(
|
||||
Label::new((&self.file_name, e.span()))
|
||||
.with_message(format!("{}", msg.fg(Color::Red)))
|
||||
.with_color(Color::Red),
|
||||
),
|
||||
};
|
||||
|
||||
report
|
||||
.finish()
|
||||
.write((&self.file_name, Source::from(&self.file_content)), w)
|
||||
}
|
||||
|
||||
fn get_ariadne_config() -> ariadne::Config {
|
||||
ariadne::Config::new().with_compact(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EiParserError {
|
||||
ParseError(Vec<ParseError>),
|
||||
InstructionLoadError(TryLoadError),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TryLoadError {
|
||||
pub index: usize,
|
||||
pub line: Line,
|
||||
pub reason: TryLoadErrorReason,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TryLoadErrorReason {
|
||||
IllegalInstruction(String),
|
||||
InvalidParameters(ParametersError),
|
||||
}
|
||||
|
||||
impl From<ParametersError> for TryLoadErrorReason {
|
||||
fn from(reason: ParametersError) -> Self {
|
||||
TryLoadErrorReason::InvalidParameters(reason)
|
||||
}
|
||||
}
|
171
echse/src/parser/ei_ast.rs
Normal file
171
echse/src/parser/ei_ast.rs
Normal file
|
@ -0,0 +1,171 @@
|
|||
use crate::machine::{RegisterIndex, RegisterOrValue};
|
||||
use crate::parser::ParseError;
|
||||
use chumsky::prelude::*;
|
||||
use std::ops::Range;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Line {
|
||||
pub instruction_name: String,
|
||||
pub parameters: Vec<(RegisterOrValue, Range<usize>)>,
|
||||
pub span: Range<usize>,
|
||||
pub instruction_name_span: Range<usize>,
|
||||
pub parameters_span: Range<usize>,
|
||||
}
|
||||
|
||||
fn radix_number(
|
||||
prefix: Option<&'static str>,
|
||||
radix: u32,
|
||||
) -> impl Parser<char, isize, Error = ParseError> {
|
||||
let no_prefix =
|
||||
text::int(radix).map(move |o: String| isize::from_str_radix(o.as_str(), radix).unwrap());
|
||||
just(prefix.unwrap_or(""))
|
||||
.ignore_then(no_prefix)
|
||||
.labelled("radix-number")
|
||||
}
|
||||
|
||||
fn multi_radix_number() -> impl Parser<char, isize, Error = ParseError> {
|
||||
let no_sign_num = radix_number(Some("0x"), 16)
|
||||
.or(radix_number(Some("0b"), 2))
|
||||
.or(radix_number(None, 10))
|
||||
.labelled("no-sign-number");
|
||||
let optional_sign = one_of("-+").labelled("sign").or_not();
|
||||
|
||||
optional_sign
|
||||
.then(no_sign_num)
|
||||
.map(
|
||||
move |(sign, num)| {
|
||||
if matches!(sign, Some('-')) {
|
||||
-num
|
||||
} else {
|
||||
num
|
||||
}
|
||||
},
|
||||
)
|
||||
.labelled("multi-radix-number")
|
||||
}
|
||||
|
||||
fn required_spaces() -> impl Parser<char, (), Error = ParseError> {
|
||||
just(' ')
|
||||
.ignored()
|
||||
.repeated()
|
||||
.at_least(1)
|
||||
.ignored()
|
||||
.labelled("required-spaces")
|
||||
}
|
||||
|
||||
fn optional_spaces() -> impl Parser<char, (), Error = ParseError> {
|
||||
just(' ')
|
||||
.ignored()
|
||||
.repeated()
|
||||
.ignored()
|
||||
.labelled("optional-spaces")
|
||||
}
|
||||
|
||||
fn register() -> impl Parser<char, RegisterIndex, Error = ParseError> {
|
||||
just("r")
|
||||
.ignore_then(text::int(10))
|
||||
.map(move |o: String| RegisterIndex(usize::from_str(&o).unwrap()))
|
||||
.labelled("register")
|
||||
}
|
||||
|
||||
fn instruction() -> impl Parser<char, Line, Error = ParseError> {
|
||||
let name = text::ident().labelled("instruction-name");
|
||||
|
||||
let value = multi_radix_number()
|
||||
.map(RegisterOrValue::Value)
|
||||
.labelled("value");
|
||||
let register = register()
|
||||
.map(RegisterOrValue::Register)
|
||||
.labelled("register");
|
||||
let value_or_register = value.or(register).labelled("value-or-register");
|
||||
|
||||
let params = value_or_register
|
||||
.map_with_span(move |o, s| (o, s))
|
||||
.separated_by(required_spaces())
|
||||
.labelled("params")
|
||||
.or_not()
|
||||
.map_with_span(|o, span| (o, span));
|
||||
|
||||
optional_spaces()
|
||||
.ignore_then(name)
|
||||
.map_with_span(|o, span| (o, span))
|
||||
.then(required_spaces().or_not().ignore_then(params))
|
||||
.map_with_span(
|
||||
|((instruction_name, instruction_name_span), (parameters, parameters_span)), span| {
|
||||
Line {
|
||||
instruction_name,
|
||||
parameters: parameters.unwrap_or_else(Vec::new),
|
||||
span,
|
||||
instruction_name_span,
|
||||
parameters_span,
|
||||
}
|
||||
},
|
||||
)
|
||||
.labelled("instruction")
|
||||
}
|
||||
|
||||
fn program() -> impl Parser<char, Vec<Line>, Error = ParseError> {
|
||||
instruction()
|
||||
.separated_by(text::newline().labelled("newline"))
|
||||
.allow_trailing()
|
||||
.collect()
|
||||
.labelled("program")
|
||||
.then_ignore(end())
|
||||
}
|
||||
|
||||
pub(crate) fn parse_program(text: &str) -> Result<Vec<Line>, Vec<ParseError>> {
|
||||
let (result, errors) = program().parse_recovery(text);
|
||||
if !errors.is_empty() {
|
||||
Err(errors)
|
||||
} else {
|
||||
Ok(result.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::machine::RegisterOrValue::{Register, Value};
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
const PROG: &str = "add 5 3 r1
|
||||
put 4 r0
|
||||
nop
|
||||
";
|
||||
let (tokens, errs) = program().parse_recovery(PROG);
|
||||
assert_eq!(Vec::<Simple<char>>::new(), errs);
|
||||
let tokens = tokens.unwrap();
|
||||
assert_eq!(
|
||||
tokens,
|
||||
vec![
|
||||
Line {
|
||||
instruction_name: "add".to_string(),
|
||||
parameters: vec![
|
||||
(Value(5), 4..5),
|
||||
(Value(3), 6..7),
|
||||
(Register(RegisterIndex(1)), 8..10)
|
||||
],
|
||||
span: 0..10,
|
||||
instruction_name_span: 0..3,
|
||||
parameters_span: 4..10,
|
||||
},
|
||||
Line {
|
||||
instruction_name: "put".to_string(),
|
||||
parameters: vec![(Value(4), 15..16), (Register(RegisterIndex::R0), 17..19)],
|
||||
span: 11..19,
|
||||
instruction_name_span: 11..14,
|
||||
parameters_span: 15..19,
|
||||
},
|
||||
Line {
|
||||
instruction_name: "nop".to_string(),
|
||||
parameters: vec![],
|
||||
span: 20..23,
|
||||
instruction_name_span: 20..23,
|
||||
parameters_span: 23..24,
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
7
echse/src/parser/mod.rs
Normal file
7
echse/src/parser/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
mod ei;
|
||||
mod ei_ast;
|
||||
|
||||
use chumsky::error::Simple;
|
||||
pub use ei::*;
|
||||
|
||||
pub type ParseError = Simple<char>;
|
8
echse_cute/Cargo.toml
Normal file
8
echse_cute/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "echse_cute"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap.workspace = true
|
||||
echse = { path = "../echse" }
|
43
echse_cute/src/main.rs
Normal file
43
echse_cute/src/main.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use echse::machine::MachineBuilder;
|
||||
use echse::parser::EiParser;
|
||||
|
||||
#[derive(clap::Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Cli {
|
||||
ei_file: String,
|
||||
#[arg(short, long, default_value_t = 2)]
|
||||
registers: usize,
|
||||
#[arg(short, long, default_value_t = false)]
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Cli = clap::Parser::parse();
|
||||
|
||||
let parser = EiParser::from_file(&args.ei_file).unwrap();
|
||||
let instructions = match parser.parse() {
|
||||
Ok(i) => i,
|
||||
Err(e) => {
|
||||
parser
|
||||
.write_error_report(e, &mut std::io::stderr())
|
||||
.expect("failed to generate error report");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut machine = MachineBuilder::new()
|
||||
.with_registers(args.registers)
|
||||
.with_instructions(instructions)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
println!("done building machine: \n{machine}");
|
||||
|
||||
while machine.step() {
|
||||
if args.verbose {
|
||||
println!("executed instruction: {machine:?}");
|
||||
}
|
||||
}
|
||||
|
||||
println!("final state: \n{machine}");
|
||||
}
|
48
flake.lock
Normal file
48
flake.lock
Normal file
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"nodes": {
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1733346208,
|
||||
"narHash": "sha256-a4WZp1xQkrnA4BbnKrzJNr+dYoQr5Xneh2syJoddFyE=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "378614f37a6bee5a3f2ef4f825a73d948d3ae921",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1733261153,
|
||||
"narHash": "sha256-eq51hyiaIwtWo19fPEeE0Zr2s83DYMKJoukNLgGGpek=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b681065d0919f7eb5309a93cea2cfa84dec9aa88",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-24.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"naersk": "naersk",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
60
flake.nix
Normal file
60
flake.nix
Normal file
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
description = "Flake for servicepoint-rustlings";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
|
||||
naersk = {
|
||||
url = "github:nix-community/naersk";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
inputs@{
|
||||
self,
|
||||
nixpkgs,
|
||||
naersk,
|
||||
}:
|
||||
let
|
||||
lib = nixpkgs.lib;
|
||||
supported-systems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"aarch64-darwin"
|
||||
"x86_64-darwin"
|
||||
];
|
||||
forAllSystems = lib.genAttrs supported-systems;
|
||||
in
|
||||
rec {
|
||||
devShells = forAllSystems (
|
||||
system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages."${system}";
|
||||
in
|
||||
{
|
||||
default = pkgs.mkShell rec {
|
||||
packages = [
|
||||
(pkgs.symlinkJoin {
|
||||
name = "rust-toolchain";
|
||||
paths = with pkgs; [
|
||||
rustc
|
||||
cargo
|
||||
rustPlatform.rustcSrc
|
||||
rustfmt
|
||||
clippy
|
||||
cargo-expand
|
||||
cargo-tarpaulin
|
||||
rustlings
|
||||
];
|
||||
})
|
||||
];
|
||||
LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath packages}";
|
||||
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
|
||||
RUST_BACKTRACE = "1";
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
formatter = forAllSystems (system: nixpkgs.legacyPackages."${system}".nixfmt-rfc-style);
|
||||
};
|
||||
}
|
9
krabbeltier/Cargo.toml
Normal file
9
krabbeltier/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "krabbeltier"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap.workspace = true
|
||||
shellwords.workspace = true
|
||||
echse = { path = "../echse" }
|
214
krabbeltier/src/main.rs
Normal file
214
krabbeltier/src/main.rs
Normal file
|
@ -0,0 +1,214 @@
|
|||
use echse::machine::{Machine, MachineBuilder};
|
||||
use echse::parser::EiParser;
|
||||
use std::collections::HashSet;
|
||||
use std::io::{BufRead, StdinLock, Write};
|
||||
|
||||
#[derive(clap::Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Cli {
|
||||
ei_file: String,
|
||||
#[arg(short, long, default_value_t = 2)]
|
||||
registers: usize,
|
||||
}
|
||||
|
||||
#[derive(clap::Parser)]
|
||||
#[command(
|
||||
disable_help_flag = true,
|
||||
disable_version_flag = true,
|
||||
no_binary_name = true,
|
||||
bin_name = ""
|
||||
)]
|
||||
enum DebugCommand {
|
||||
#[command(visible_alias = "q")]
|
||||
/// exit the debugger
|
||||
Quit,
|
||||
|
||||
#[command(visible_alias = "p")]
|
||||
/// prints the current state
|
||||
Print {
|
||||
#[arg(value_enum, default_value_t = PrintMode::All)]
|
||||
mode: PrintMode,
|
||||
},
|
||||
|
||||
#[command(visible_alias = "s")]
|
||||
/// executes a single instruction
|
||||
Step,
|
||||
|
||||
#[command(visible_alias = "j")]
|
||||
/// set the instruction pointer
|
||||
Jump { ip: usize },
|
||||
|
||||
#[command(visible_alias = "b")]
|
||||
/// toggle breakpoints
|
||||
Break { ip: Option<usize> },
|
||||
|
||||
#[command(visible_alias = "bl")]
|
||||
BreakList,
|
||||
|
||||
/// continue running instructions
|
||||
#[command(visible_alias = "c")]
|
||||
Continue,
|
||||
|
||||
/// Write a value into the specified register
|
||||
#[command(visible_alias = "w")]
|
||||
Write {
|
||||
register: usize,
|
||||
#[arg(allow_negative_numbers = true)]
|
||||
value: isize,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum, Default, Debug)]
|
||||
enum PrintMode {
|
||||
#[default]
|
||||
#[clap(alias = "a")]
|
||||
All,
|
||||
#[clap(alias = "r")]
|
||||
Registers,
|
||||
#[clap(alias = "i")]
|
||||
Instructions,
|
||||
}
|
||||
|
||||
struct App {
|
||||
machine: Machine,
|
||||
breakpoints: HashSet<usize>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub(crate) fn exec(&mut self) {
|
||||
println!("Welcome to the debugger. Run 'help' to see available commands.");
|
||||
|
||||
let mut stdin = std::io::stdin().lock();
|
||||
let mut input_buf = String::new();
|
||||
|
||||
loop {
|
||||
Self::read_command_line(&mut stdin, &mut input_buf);
|
||||
let command = shellwords::split(input_buf.trim()).map(clap::Parser::try_parse_from);
|
||||
|
||||
match command {
|
||||
Ok(Ok(command)) => {
|
||||
if !self.run_command(command) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{e}");
|
||||
}
|
||||
Ok(Err(e)) => println!("{e}"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn read_command_line(stdin: &mut StdinLock, input_buf: &mut String) {
|
||||
input_buf.clear();
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
stdout.write_fmt(format_args!("> ")).unwrap();
|
||||
stdout.flush().unwrap();
|
||||
drop(stdout);
|
||||
stdin.read_line(input_buf).unwrap();
|
||||
}
|
||||
|
||||
fn run_command(&mut self, command: DebugCommand) -> bool {
|
||||
match command {
|
||||
DebugCommand::Quit => return false,
|
||||
DebugCommand::Print { mode } => match mode {
|
||||
PrintMode::All => {
|
||||
println!("{}", self.machine);
|
||||
}
|
||||
PrintMode::Registers => {
|
||||
println!("{}", self.machine.registers);
|
||||
}
|
||||
PrintMode::Instructions => {
|
||||
println!("next instruction is {}", self.machine.ip);
|
||||
println!("{}", self.machine.instructions);
|
||||
}
|
||||
},
|
||||
DebugCommand::Step => {
|
||||
if self.machine.instructions.len() > self.machine.ip {
|
||||
println!(
|
||||
"{:03}: {}\n",
|
||||
self.machine.ip, self.machine.instructions[self.machine.ip]
|
||||
);
|
||||
self.machine.step();
|
||||
} else {
|
||||
println!("reached end of program\n");
|
||||
}
|
||||
}
|
||||
DebugCommand::Jump { ip } => {
|
||||
self.machine.ip = ip;
|
||||
println!("ip now at {ip}\n");
|
||||
}
|
||||
DebugCommand::Break { ip } => {
|
||||
let ip = ip.unwrap_or(self.machine.ip);
|
||||
if self.breakpoints.contains(&ip) {
|
||||
self.breakpoints.remove(&ip);
|
||||
println!("removed breakpoint at {ip}\n");
|
||||
} else {
|
||||
self.breakpoints.insert(ip);
|
||||
println!("added breakpoint at {ip}\n");
|
||||
}
|
||||
}
|
||||
DebugCommand::BreakList => {
|
||||
println!("current breakpoints: {:?}\n", self.breakpoints);
|
||||
}
|
||||
DebugCommand::Continue => {
|
||||
let mut count = 0;
|
||||
while self.machine.step() {
|
||||
count += 1;
|
||||
if self.breakpoints.contains(&self.machine.ip) {
|
||||
println!(
|
||||
"breakpoint hit before executing {:03}: {}",
|
||||
self.machine.ip, self.machine.instructions[self.machine.ip]
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"stepped {count} instructions, ip now at {}\n",
|
||||
self.machine.ip
|
||||
);
|
||||
}
|
||||
DebugCommand::Write { value, register } => {
|
||||
if self.machine.registers.len() <= register {
|
||||
eprintln!(
|
||||
"Cannot write to register r{register}, as this machine only has {}\n",
|
||||
self.machine.registers.len()
|
||||
);
|
||||
} else {
|
||||
self.machine.registers[register].write(value);
|
||||
println!("r{register}: {value}\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Cli = clap::Parser::parse();
|
||||
|
||||
let parser = EiParser::from_file(&args.ei_file).unwrap();
|
||||
let instructions = match parser.parse() {
|
||||
Ok(i) => i,
|
||||
Err(e) => {
|
||||
parser
|
||||
.write_error_report(e, &mut std::io::stderr())
|
||||
.expect("failed to generate error report");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let machine = MachineBuilder::new()
|
||||
.with_registers(args.registers)
|
||||
.with_instructions(instructions)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut app = App {
|
||||
machine,
|
||||
breakpoints: HashSet::new(),
|
||||
};
|
||||
|
||||
app.exec();
|
||||
}
|
7
the_answer.ei
Executable file
7
the_answer.ei
Executable file
|
@ -0,0 +1,7 @@
|
|||
put 23 r0
|
||||
div r0 10 r1
|
||||
add r1 r1 r1
|
||||
add r0 r0 r0
|
||||
sub r0 r1 r0
|
||||
mod r1 3 r1
|
||||
add r1 r0 r1
|
Loading…
Reference in a new issue