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