Initial commit
This commit is contained in:
commit
722cdbc2f8
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
BIN
68k_ins.pdf
Normal file
BIN
68k_ins.pdf
Normal file
Binary file not shown.
940
Cargo.lock
generated
Normal file
940
Cargo.lock
generated
Normal file
@ -0,0 +1,940 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atty"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitvec"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b"
|
||||||
|
dependencies = [
|
||||||
|
"funty",
|
||||||
|
"radium",
|
||||||
|
"tap",
|
||||||
|
"wyz",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.73"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
|
||||||
|
dependencies = [
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"time",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "3.2.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750"
|
||||||
|
dependencies = [
|
||||||
|
"atty",
|
||||||
|
"bitflags",
|
||||||
|
"clap_lex",
|
||||||
|
"indexmap",
|
||||||
|
"strsim",
|
||||||
|
"termcolor",
|
||||||
|
"textwrap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||||
|
dependencies = [
|
||||||
|
"os_str_bytes",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.23.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2102ea4f781910f8a5b98dd061f4c2023f479ce7bb1236330099ceb5a93cf17"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"serde",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm_winapi"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctor"
|
||||||
|
version = "0.1.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive-try-from-primitive"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "302ccf094df1151173bb6f5a2282fcd2f45accd5eae1bdf82dcbfefbc501ad5c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
|
||||||
|
dependencies = [
|
||||||
|
"errno-dragonfly",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno-dragonfly"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fd-lock"
|
||||||
|
version = "3.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e11dcc7e4d79a8c89b9ab4c6f5c30b1fc4a83c420792da3542fd31179ed5f517"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "funty"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ghost"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb19fe8de3ea0920d282f7b77dd4227aea6b8b999b42cdf0ca41b2472b14443a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "human-repr"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d0fb489e3108b684bba475a4cb9804260365870e2f60058265e3d0a3b309bdb1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.48"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "237a0714f28b1ee39ccec0770ccb544eb02c9ef2c82bb096230eefcffa6468b0"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"js-sys",
|
||||||
|
"once_cell",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inventory"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30a61b8101d87996f82d725ba701b1987b7afc72f481c13513a30b855b9c9133"
|
||||||
|
dependencies = [
|
||||||
|
"ctor",
|
||||||
|
"ghost",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "io-lifetimes"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.133"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.0.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "m68k_emu"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bitvec",
|
||||||
|
"derive-try-from-primitive",
|
||||||
|
"human-repr",
|
||||||
|
"inventory",
|
||||||
|
"itertools",
|
||||||
|
"nullable-result",
|
||||||
|
"parse_int",
|
||||||
|
"paste",
|
||||||
|
"reedline-repl-rs",
|
||||||
|
"serde",
|
||||||
|
"serde_yaml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-ansi-term"
|
||||||
|
version = "0.45.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7bca0d33a384280d1563b97f49cb95303df9fa22588739a04b7d8015c1ccd50"
|
||||||
|
dependencies = [
|
||||||
|
"overload",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nullable-result"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "655c9e354a7e9dda520befe8d617323b3479a4c99ab6530d62ba1be5e946b999"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_str_bytes"
|
||||||
|
version = "6.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "overload"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parse_int"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2d695b79916a2c08bcff7be7647ab60d1402885265005a6658ffe6d763553c5a"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "radium"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reedline"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "422f144c06f679da4ab4f082a6d1d43e28bfabb68d009100e6e5520728f99fec"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"crossterm",
|
||||||
|
"fd-lock",
|
||||||
|
"nu-ansi-term",
|
||||||
|
"serde",
|
||||||
|
"strip-ansi-escapes",
|
||||||
|
"strum",
|
||||||
|
"strum_macros",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reedline-repl-rs"
|
||||||
|
version = "1.0.2"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"crossterm",
|
||||||
|
"nu-ansi-term",
|
||||||
|
"reedline",
|
||||||
|
"regex",
|
||||||
|
"winapi-util",
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.35.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af895b90e5c071badc3136fc10ff0bcfc98747eadbaf43ed8f214e07ba8f8477"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"io-lifetimes",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.144"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.144"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_yaml"
|
||||||
|
version = "0.9.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8613d593412a0deb7bbd8de9d908efff5a0cb9ccd8f62c641e7b2ed2f57291d1"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
"unsafe-libyaml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-mio"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strip-ansi-escapes"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8"
|
||||||
|
dependencies = [
|
||||||
|
"vte",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.24.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustversion",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.93"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04066589568b72ec65f42d65a1a52436e954b168773148893c020269563decf2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tap"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.15.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unsafe-libyaml"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vte"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
"utf8parse",
|
||||||
|
"vte_generate_state_changes",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vte_generate_state_changes"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.83"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.83"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.83"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.83"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.83"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.36.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.36.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.36.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.36.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.36.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.36.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wyz"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e"
|
||||||
|
dependencies = [
|
||||||
|
"tap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yansi"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
19
Cargo.toml
Normal file
19
Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "m68k_emu"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bitvec = "1.0.0"
|
||||||
|
derive-try-from-primitive = "1.0.0"
|
||||||
|
human-repr = { version = "1.0.1", features = ["iec", "space"] }
|
||||||
|
inventory = "0.3.1"
|
||||||
|
itertools = "0.10.5"
|
||||||
|
nullable-result = { version = "0.7.0", features=["try_trait"] }
|
||||||
|
parse_int = "0.6.0"
|
||||||
|
paste = "1.0.9"
|
||||||
|
reedline-repl-rs = { path = "reedline-repl-rs" }
|
||||||
|
serde = { version = "1.0.144", features = ["derive"] }
|
||||||
|
serde_yaml = "0.9.13"
|
66997
M68000PRM.pdf
Normal file
66997
M68000PRM.pdf
Normal file
File diff suppressed because it is too large
Load Diff
2
build_rom.sh
Normal file
2
build_rom.sh
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
vasmm68k_std rom.68k -o rom.bin -Fbin
|
||||||
|
|
8
config.yaml
Normal file
8
config.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
cards:
|
||||||
|
- type: rom
|
||||||
|
image: rom.bin
|
||||||
|
- type: ram
|
||||||
|
size: 4096
|
||||||
|
- type: storage
|
||||||
|
disk: pcrel.bin
|
BIN
m68k_opcode_chart.pdf
Normal file
BIN
m68k_opcode_chart.pdf
Normal file
Binary file not shown.
53
ram_map.c
Executable file
53
ram_map.c
Executable file
@ -0,0 +1,53 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
uint8_t find_all_ram_cards(uint8_t* buf /* a1 */) {
|
||||||
|
uint8_t len = 0; // move.b 0, d0
|
||||||
|
void* curr_card = (void*)0xff0000; // a0 = 0xff0000
|
||||||
|
while(1) { // farc_loop:
|
||||||
|
curr_card += 0x100; // lea (0x100, a0), a0
|
||||||
|
uint8_t type = *((uint8_t*)(curr_card+0xff)); // move.b (0xff, a0), d0
|
||||||
|
if (type == 0) { // beq.b farc_done
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (type != 2) { // cmp.b #0x2, d0; bne.b farc_loop
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
*buf = (uint8_t)((uint32_t)curr_card>>16);
|
||||||
|
buf += 1; // inc a1
|
||||||
|
len += 1; // inc d1
|
||||||
|
} // bra.b farc_loop
|
||||||
|
// farc_done:
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sort_ram_cards(uint8_t* buf) {
|
||||||
|
uint8_t len = find_all_ram_cards(buf);
|
||||||
|
// Insertion sort buf using the aligment of the cards
|
||||||
|
uint8_t i = 1;
|
||||||
|
while(1) {
|
||||||
|
if (i >= len) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uint8_t num = buf[i];
|
||||||
|
void* card = (void*)((uint32_t)num<<16);
|
||||||
|
*((uint32_t*)card)=0xfffffffe;
|
||||||
|
uint32_t x = ~(*((uint32_t*)card)) + 1;
|
||||||
|
uint8_t j = i - 1;
|
||||||
|
while(1) {
|
||||||
|
if (j == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uint8_t num = buf[j];
|
||||||
|
void* card = (void*)((uint32_t)num<<16);
|
||||||
|
*((uint32_t*)card)=0xfffffffe;
|
||||||
|
uint32_t aj = ~(*((uint32_t*)card)) + 1;
|
||||||
|
if (aj <= x) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
buf[j + 1] = buf[j];
|
||||||
|
j = j - 1;
|
||||||
|
}
|
||||||
|
buf[j + 1] = buf[i];
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
}
|
2
reedline-repl-rs/.gitignore
vendored
Normal file
2
reedline-repl-rs/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
Cargo.lock
|
41
reedline-repl-rs/Cargo.toml
Normal file
41
reedline-repl-rs/Cargo.toml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
[package]
|
||||||
|
name = "reedline-repl-rs"
|
||||||
|
version = "1.0.2"
|
||||||
|
authors = ["Artur Hallmann <arturh@arturh.de>", "Jack Lund <jackl@geekheads.net>"]
|
||||||
|
description = "Library to generate a fancy REPL for your application based on reedline and clap"
|
||||||
|
license = "MIT"
|
||||||
|
repository = "https://github.com/arturh85/reedline-repl-rs"
|
||||||
|
homepage = "https://github.com/arturh85/reedline-repl-rs"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = ["repl", "interpreter", "clap"]
|
||||||
|
categories = ["command-line-interface"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
reedline = "0.6.0"
|
||||||
|
nu-ansi-term = { version = "0.45.1" }
|
||||||
|
crossterm = { version = "0.23.2" }
|
||||||
|
yansi = "0.5.1"
|
||||||
|
regex = "1"
|
||||||
|
clap = "3"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tokio = { version = "1", features = ["macros", "rt-multi-thread"] } # only for async example
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
winapi-util = "0.1.5"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
async = []
|
||||||
|
macro = ["clap/cargo"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "async"
|
||||||
|
required-features = ["async"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "macro"
|
||||||
|
required-features = ["macro"]
|
28
reedline-repl-rs/examples/async.rs
Normal file
28
reedline-repl-rs/examples/async.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//! Example using Repl with a custom error type.
|
||||||
|
use reedline_repl_rs::clap::{Arg, ArgMatches, Command};
|
||||||
|
use reedline_repl_rs::{Repl, Result};
|
||||||
|
|
||||||
|
/// Write "Hello" with given name
|
||||||
|
async fn hello<T>(args: ArgMatches, _context: &mut T) -> Result<Option<String>> {
|
||||||
|
Ok(Some(format!("Hello, {}", args.value_of("who").unwrap())))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called after successful command execution, updates prompt with returned Option
|
||||||
|
async fn update_prompt<T>(_context: &mut T) -> Result<Option<String>> {
|
||||||
|
Ok(Some("updated".to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let mut repl = Repl::new(())
|
||||||
|
.with_name("MyApp")
|
||||||
|
.with_version("v0.1.0")
|
||||||
|
.with_command_async(
|
||||||
|
Command::new("hello")
|
||||||
|
.arg(Arg::new("who").required(true))
|
||||||
|
.about("Greetings!"),
|
||||||
|
|args, context| Box::pin(hello(args, context)),
|
||||||
|
)
|
||||||
|
.with_on_after_command_async(|context| Box::pin(update_prompt(context)));
|
||||||
|
repl.run_async().await
|
||||||
|
}
|
45
reedline-repl-rs/examples/custom_error.rs
Normal file
45
reedline-repl-rs/examples/custom_error.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
//! Example using Repl with a custom error type.
|
||||||
|
|
||||||
|
use reedline_repl_rs::clap::{ArgMatches, Command};
|
||||||
|
use reedline_repl_rs::Repl;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum CustomError {
|
||||||
|
ReplError(reedline_repl_rs::Error),
|
||||||
|
StringError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<reedline_repl_rs::Error> for CustomError {
|
||||||
|
fn from(e: reedline_repl_rs::Error) -> Self {
|
||||||
|
CustomError::ReplError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for CustomError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
CustomError::ReplError(e) => write!(f, "REPL Error: {}", e),
|
||||||
|
CustomError::StringError(s) => write!(f, "String Error: {}", s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for CustomError {}
|
||||||
|
|
||||||
|
/// Do nothing, unsuccesfully
|
||||||
|
fn hello<T>(_args: ArgMatches, _context: &mut T) -> Result<Option<String>, CustomError> {
|
||||||
|
Err(CustomError::StringError("Returning an error".to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), reedline_repl_rs::Error> {
|
||||||
|
let mut repl = Repl::new(())
|
||||||
|
.with_name("MyApp")
|
||||||
|
.with_version("v0.1.0")
|
||||||
|
.with_description("My very cool app")
|
||||||
|
.with_command(
|
||||||
|
Command::new("hello").about("Do nothing, unsuccessfully"),
|
||||||
|
hello,
|
||||||
|
);
|
||||||
|
repl.run()
|
||||||
|
}
|
64
reedline-repl-rs/examples/custom_keybinding.rs
Normal file
64
reedline-repl-rs/examples/custom_keybinding.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
//! Example with custom Keybinding
|
||||||
|
use crossterm::event::{KeyCode, KeyModifiers};
|
||||||
|
use reedline::{EditCommand, ReedlineEvent};
|
||||||
|
use reedline_repl_rs::clap::{Arg, ArgMatches, Command};
|
||||||
|
use reedline_repl_rs::{Repl, Result};
|
||||||
|
|
||||||
|
/// Write "Hello" with given name
|
||||||
|
fn hello<T>(args: ArgMatches, _context: &mut T) -> Result<Option<String>> {
|
||||||
|
Ok(Some(format!("Hello, {}", args.value_of("who").unwrap())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let mut repl = Repl::new(())
|
||||||
|
.with_name("MyApp")
|
||||||
|
.with_version("v0.1.0")
|
||||||
|
.with_description("My very cool app")
|
||||||
|
.with_banner("Welcome to MyApp")
|
||||||
|
.with_command(
|
||||||
|
Command::new("hello")
|
||||||
|
.arg(Arg::new("who").required(true))
|
||||||
|
.about("Greetings!"),
|
||||||
|
hello,
|
||||||
|
)
|
||||||
|
// greet friend with CTRG+g
|
||||||
|
.with_keybinding(
|
||||||
|
KeyModifiers::CONTROL,
|
||||||
|
KeyCode::Char('g'),
|
||||||
|
ReedlineEvent::ExecuteHostCommand("hello Friend".to_string()),
|
||||||
|
)
|
||||||
|
// show help with CTRL+h
|
||||||
|
.with_keybinding(
|
||||||
|
KeyModifiers::CONTROL,
|
||||||
|
KeyCode::Char('h'),
|
||||||
|
ReedlineEvent::ExecuteHostCommand("help".to_string()),
|
||||||
|
)
|
||||||
|
// uppercase current word with CTRL+u
|
||||||
|
.with_keybinding(
|
||||||
|
KeyModifiers::CONTROL,
|
||||||
|
KeyCode::Char('u'),
|
||||||
|
ReedlineEvent::Edit(vec![EditCommand::UppercaseWord]),
|
||||||
|
)
|
||||||
|
// uppercase current word with CTRL+l
|
||||||
|
.with_keybinding(
|
||||||
|
KeyModifiers::CONTROL,
|
||||||
|
KeyCode::Char('l'),
|
||||||
|
ReedlineEvent::Edit(vec![EditCommand::LowercaseWord]),
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("Keybindings:");
|
||||||
|
let keybindings = repl.get_keybindings();
|
||||||
|
for search_modifier in [
|
||||||
|
KeyModifiers::NONE,
|
||||||
|
KeyModifiers::CONTROL,
|
||||||
|
KeyModifiers::SHIFT,
|
||||||
|
KeyModifiers::ALT,
|
||||||
|
] {
|
||||||
|
for ((modifier, key_code), reedline_event) in &keybindings {
|
||||||
|
if *modifier == search_modifier {
|
||||||
|
println!("{:?} + {:?} => {:?}", modifier, key_code, reedline_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repl.run()
|
||||||
|
}
|
23
reedline-repl-rs/examples/hello_world.rs
Normal file
23
reedline-repl-rs/examples/hello_world.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
//! Minimal example
|
||||||
|
use reedline_repl_rs::clap::{Arg, ArgMatches, Command};
|
||||||
|
use reedline_repl_rs::{Repl, Result};
|
||||||
|
|
||||||
|
/// Write "Hello" with given name
|
||||||
|
fn hello<T>(args: ArgMatches, _context: &mut T) -> Result<Option<String>> {
|
||||||
|
Ok(Some(format!("Hello, {}", args.value_of("who").unwrap())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let mut repl = Repl::new(())
|
||||||
|
.with_name("MyApp")
|
||||||
|
.with_version("v0.1.0")
|
||||||
|
.with_description("My very cool app")
|
||||||
|
.with_banner("Welcome to MyApp")
|
||||||
|
.with_command(
|
||||||
|
Command::new("hello")
|
||||||
|
.arg(Arg::new("who").required(true))
|
||||||
|
.about("Greetings!"),
|
||||||
|
hello,
|
||||||
|
);
|
||||||
|
repl.run()
|
||||||
|
}
|
18
reedline-repl-rs/examples/macro.rs
Normal file
18
reedline-repl-rs/examples/macro.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
//! Example using initialize_repl macro
|
||||||
|
use reedline_repl_rs::clap::{Arg, ArgMatches, Command};
|
||||||
|
use reedline_repl_rs::{initialize_repl, Repl, Result};
|
||||||
|
|
||||||
|
/// Write "Hello" with given name
|
||||||
|
fn hello<T>(args: ArgMatches, _context: &mut T) -> Result<Option<String>> {
|
||||||
|
Ok(Some(format!("Hello, {}", args.value_of("who").unwrap())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let mut repl = initialize_repl!(()).with_command(
|
||||||
|
Command::new("hello")
|
||||||
|
.arg(Arg::new("who").required(true))
|
||||||
|
.about("Greetings!"),
|
||||||
|
hello,
|
||||||
|
);
|
||||||
|
repl.run()
|
||||||
|
}
|
37
reedline-repl-rs/examples/no_context.rs
Normal file
37
reedline-repl-rs/examples/no_context.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
//! Example using Repl without Context (or, more precisely, a Context of ())
|
||||||
|
use reedline_repl_rs::clap::{Arg, ArgMatches, Command};
|
||||||
|
use reedline_repl_rs::{Repl, Result};
|
||||||
|
|
||||||
|
/// Add two numbers. Have to make this generic to be able to pass a Context of type ()
|
||||||
|
fn add<T>(args: ArgMatches, _context: &mut T) -> Result<Option<String>> {
|
||||||
|
let first: i32 = args.value_of("first").unwrap().parse()?;
|
||||||
|
let second: i32 = args.value_of("second").unwrap().parse()?;
|
||||||
|
|
||||||
|
Ok(Some((first + second).to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write "Hello"
|
||||||
|
fn hello<T>(args: ArgMatches, _context: &mut T) -> Result<Option<String>> {
|
||||||
|
Ok(Some(format!("Hello, {}", args.value_of("who").unwrap())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let mut repl = Repl::new(())
|
||||||
|
.with_name("MyApp")
|
||||||
|
.with_version("v0.1.0")
|
||||||
|
.with_description("My very cool app")
|
||||||
|
.with_command(
|
||||||
|
Command::new("add")
|
||||||
|
.arg(Arg::new("first").required(true))
|
||||||
|
.arg(Arg::new("second").required(true))
|
||||||
|
.about("Add two numbers together"),
|
||||||
|
add,
|
||||||
|
)
|
||||||
|
.with_command(
|
||||||
|
Command::new("hello")
|
||||||
|
.arg(Arg::new("who").required(true))
|
||||||
|
.about("Greetings!"),
|
||||||
|
hello,
|
||||||
|
);
|
||||||
|
repl.run()
|
||||||
|
}
|
48
reedline-repl-rs/examples/with_context.rs
Normal file
48
reedline-repl-rs/examples/with_context.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
//! Example using Repl with Context
|
||||||
|
use reedline_repl_rs::clap::{Arg, ArgMatches, Command};
|
||||||
|
use reedline_repl_rs::{Repl, Result};
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Context {
|
||||||
|
list: VecDeque<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append name to list
|
||||||
|
fn append(args: ArgMatches, context: &mut Context) -> Result<Option<String>> {
|
||||||
|
let name: String = args.value_of("name").unwrap().to_string();
|
||||||
|
context.list.push_back(name);
|
||||||
|
let list: Vec<String> = context.list.clone().into();
|
||||||
|
|
||||||
|
Ok(Some(list.join(", ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepend name to list
|
||||||
|
fn prepend(args: ArgMatches, context: &mut Context) -> Result<Option<String>> {
|
||||||
|
let name: String = args.value_of("name").unwrap().to_string();
|
||||||
|
context.list.push_front(name);
|
||||||
|
let list: Vec<String> = context.list.clone().into();
|
||||||
|
|
||||||
|
Ok(Some(list.join(", ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let mut repl = Repl::new(Context::default())
|
||||||
|
.with_name("MyList")
|
||||||
|
.with_version("v0.1.0")
|
||||||
|
.with_description("My very cool List")
|
||||||
|
.with_command(
|
||||||
|
Command::new("append")
|
||||||
|
.arg(Arg::new("name").required(true))
|
||||||
|
.about("Append name to end of list"),
|
||||||
|
append,
|
||||||
|
)
|
||||||
|
.with_command(
|
||||||
|
Command::new("prepend")
|
||||||
|
.arg(Arg::new("name").required(true))
|
||||||
|
.about("Prepend name to front of list"),
|
||||||
|
prepend,
|
||||||
|
)
|
||||||
|
.with_on_after_command(|context| Ok(Some(format!("MyList [{}]", context.list.len()))));
|
||||||
|
repl.run()
|
||||||
|
}
|
11
reedline-repl-rs/renovate.json
Normal file
11
reedline-repl-rs/renovate.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"config:base"
|
||||||
|
],
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"matchUpdateTypes": ["major","minor", "patch", "pin", "digest"],
|
||||||
|
"automerge": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
55
reedline-repl-rs/src/command.rs
Normal file
55
reedline-repl-rs/src/command.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#[cfg(feature = "async")]
|
||||||
|
use crate::AsyncCallback;
|
||||||
|
use crate::Callback;
|
||||||
|
use clap::Command;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// Struct to define a command in the REPL
|
||||||
|
|
||||||
|
pub(crate) struct ReplCommand<Context, E> {
|
||||||
|
pub(crate) name: String,
|
||||||
|
pub(crate) command: Command<'static>,
|
||||||
|
pub(crate) callback: Option<Callback<Context, E>>,
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
pub(crate) async_callback: Option<AsyncCallback<Context, E>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Context, E> fmt::Debug for ReplCommand<Context, E> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Command").field("name", &self.name).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Context, E> PartialEq for ReplCommand<Context, E> {
|
||||||
|
fn eq(&self, other: &ReplCommand<Context, E>) -> bool {
|
||||||
|
self.name == other.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Context, E> ReplCommand<Context, E> {
|
||||||
|
/// Create a new command with the given name and callback function
|
||||||
|
pub fn new(name: &str, command: Command<'static>, callback: Callback<Context, E>) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.to_string(),
|
||||||
|
command,
|
||||||
|
callback: Some(callback),
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
async_callback: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new async command with the given name and callback function
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
pub fn new_async(
|
||||||
|
name: &str,
|
||||||
|
command: Command<'static>,
|
||||||
|
callback: AsyncCallback<Context, E>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.to_string(),
|
||||||
|
command,
|
||||||
|
callback: None,
|
||||||
|
async_callback: Some(callback),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
110
reedline-repl-rs/src/completer.rs
Normal file
110
reedline-repl-rs/src/completer.rs
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
use crate::command::ReplCommand;
|
||||||
|
use clap::Command;
|
||||||
|
use reedline::{Completer, Span, Suggestion};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub(crate) struct ReplCompleter {
|
||||||
|
commands: HashMap<String, Command<'static>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Completer for ReplCompleter {
|
||||||
|
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||||
|
let mut completions = vec![];
|
||||||
|
completions.extend(if line.contains(' ') {
|
||||||
|
let mut words = line[0..pos].split(' ');
|
||||||
|
let first_word = words.next().unwrap();
|
||||||
|
let mut words_rev = words.rev();
|
||||||
|
if let Some(command) = self.commands.get(first_word) {
|
||||||
|
let last_word = words_rev.next().unwrap();
|
||||||
|
let last_word_start_pos = line.len() - last_word.len();
|
||||||
|
let span = Span::new(last_word_start_pos, pos);
|
||||||
|
self.parameter_values_starting_with(command, words_rev.count(), last_word, span)
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let span = Span::new(0, pos);
|
||||||
|
self.commands_starting_with(line, span)
|
||||||
|
});
|
||||||
|
completions.dedup();
|
||||||
|
completions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReplCompleter {
|
||||||
|
pub fn new<Context, E>(repl_commands: &HashMap<String, ReplCommand<Context, E>>) -> Self {
|
||||||
|
let mut commands = HashMap::new();
|
||||||
|
for (name, repl_command) in repl_commands.iter() {
|
||||||
|
commands.insert(name.clone(), repl_command.command.clone());
|
||||||
|
}
|
||||||
|
ReplCompleter { commands }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_suggestion(&self, value: &str, help: Option<&str>, span: Span) -> Suggestion {
|
||||||
|
Suggestion {
|
||||||
|
value: value.to_string(),
|
||||||
|
description: help.map(|n| n.to_string()),
|
||||||
|
extra: None,
|
||||||
|
span,
|
||||||
|
append_whitespace: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parameter_values_starting_with(
|
||||||
|
&self,
|
||||||
|
command: &Command<'static>,
|
||||||
|
_parameter_idx: usize,
|
||||||
|
search: &str,
|
||||||
|
span: Span,
|
||||||
|
) -> Vec<Suggestion> {
|
||||||
|
let mut completions = vec![];
|
||||||
|
for arg in command.get_arguments() {
|
||||||
|
// skips --help and --version
|
||||||
|
if arg.is_global_set() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(possible_values) = arg.get_possible_values() {
|
||||||
|
completions.extend(
|
||||||
|
possible_values
|
||||||
|
.iter()
|
||||||
|
.filter(|value| value.get_name().starts_with(search))
|
||||||
|
.map(|value| {
|
||||||
|
self.build_suggestion(value.get_name(), value.get_help(), span)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(long) = arg.get_long() {
|
||||||
|
let value = "--".to_string() + long;
|
||||||
|
if value.starts_with(search) {
|
||||||
|
completions.push(self.build_suggestion(&value, arg.get_help(), span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(short) = arg.get_short() {
|
||||||
|
let value = "-".to_string() + &short.to_string();
|
||||||
|
if value.starts_with(search) {
|
||||||
|
completions.push(self.build_suggestion(&value, arg.get_help(), span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
completions
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commands_starting_with(&self, search: &str, span: Span) -> Vec<Suggestion> {
|
||||||
|
let mut result: Vec<Suggestion> = self
|
||||||
|
.commands
|
||||||
|
.iter()
|
||||||
|
.filter(|(key, _)| key.starts_with(search))
|
||||||
|
.map(|(_, command)| {
|
||||||
|
self.build_suggestion(command.get_name(), command.get_about(), span)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if "help".starts_with(search) {
|
||||||
|
result.push(self.build_suggestion("help", Some("show help"), span));
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
81
reedline-repl-rs/src/error.rs
Normal file
81
reedline-repl-rs/src/error.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
use std::convert::From;
|
||||||
|
use std::fmt;
|
||||||
|
use std::num;
|
||||||
|
|
||||||
|
/// Result type
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// Error type
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Parameter is required when it shouldn't be
|
||||||
|
IllegalRequiredError(String),
|
||||||
|
|
||||||
|
/// Parameter is defaulted when it's also required
|
||||||
|
IllegalDefaultError(String),
|
||||||
|
|
||||||
|
/// A required argument is missing
|
||||||
|
MissingRequiredArgument(String, String),
|
||||||
|
|
||||||
|
/// Too many arguments were provided
|
||||||
|
TooManyArguments(String, usize),
|
||||||
|
|
||||||
|
/// Error parsing a bool value
|
||||||
|
ParseBoolError(std::str::ParseBoolError),
|
||||||
|
|
||||||
|
/// Error parsing an int value
|
||||||
|
ParseIntError(num::ParseIntError),
|
||||||
|
|
||||||
|
/// Error parsing a float value
|
||||||
|
ParseFloatError(num::ParseFloatError),
|
||||||
|
|
||||||
|
/// Command not found
|
||||||
|
UnknownCommand(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> {
|
||||||
|
match self {
|
||||||
|
Error::IllegalDefaultError(parameter) => {
|
||||||
|
write!(f, "Error: Parameter '{}' cannot have a default", parameter)
|
||||||
|
}
|
||||||
|
Error::IllegalRequiredError(parameter) => {
|
||||||
|
write!(f, "Error: Parameter '{}' cannot be required", parameter)
|
||||||
|
}
|
||||||
|
Error::MissingRequiredArgument(command, parameter) => write!(
|
||||||
|
f,
|
||||||
|
"Error: Missing required argument '{}' for command '{}'",
|
||||||
|
parameter, command
|
||||||
|
),
|
||||||
|
Error::TooManyArguments(command, nargs) => write!(
|
||||||
|
f,
|
||||||
|
"Error: Command '{}' can have no more than {} arguments",
|
||||||
|
command, nargs,
|
||||||
|
),
|
||||||
|
Error::ParseBoolError(error) => write!(f, "Error: {}", error,),
|
||||||
|
Error::ParseFloatError(error) => write!(f, "Error: {}", error,),
|
||||||
|
Error::ParseIntError(error) => write!(f, "Error: {}", error,),
|
||||||
|
Error::UnknownCommand(command) => write!(f, "Error: Unknown command '{}'", command),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<num::ParseIntError> for Error {
|
||||||
|
fn from(error: num::ParseIntError) -> Self {
|
||||||
|
Error::ParseIntError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<num::ParseFloatError> for Error {
|
||||||
|
fn from(error: num::ParseFloatError) -> Self {
|
||||||
|
Error::ParseFloatError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::str::ParseBoolError> for Error {
|
||||||
|
fn from(error: std::str::ParseBoolError) -> Self {
|
||||||
|
Error::ParseBoolError(error)
|
||||||
|
}
|
||||||
|
}
|
165
reedline-repl-rs/src/lib.rs
Normal file
165
reedline-repl-rs/src/lib.rs
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
//! reedline-repl-rs - [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) library
|
||||||
|
//! for Rust
|
||||||
|
//!
|
||||||
|
//! # Example
|
||||||
|
//! ```rust,no_run
|
||||||
|
#![doc = include_str!("../examples/hello_world.rs")]
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! reedline-repl-rs uses the [builder](https://en.wikipedia.org/wiki/Builder_pattern) pattern extensively.
|
||||||
|
//! What these lines are doing is:
|
||||||
|
//! - creating a repl with an empty Context (see below)
|
||||||
|
//! - with a name of "MyApp", the given version, and the given description
|
||||||
|
//! - and adding a "hello" command which calls out to the `hello` callback function defined above
|
||||||
|
//! - the `hello` command has a single parameter, "who", which is required, and has the given help
|
||||||
|
//! message
|
||||||
|
//!
|
||||||
|
//! The `hello` function takes a reference to [ArgMatches](https://docs.rs/clap/latest/clap/struct.ArgMatches.html),
|
||||||
|
//! and an (unused) `Context`, which is used to hold state if you
|
||||||
|
//! need to - the initial context is passed in to the call to
|
||||||
|
//! [Repl::new](struct.Repl.html#method.new), in our case, `()`.
|
||||||
|
//! Because we're not using a Context, we need to include a generic type in our `hello` function,
|
||||||
|
//! because there's no way to pass an argument of type `()` otherwise.
|
||||||
|
//!
|
||||||
|
//! All command function callbacks return a `Result<Option<String>>`. This has the following
|
||||||
|
//! effect:
|
||||||
|
//! - If the return is `Ok(Some(String))`, it prints the string to stdout
|
||||||
|
//! - If the return is `Ok(None)`, it prints nothing
|
||||||
|
//! - If the return is an error, it prints the error message to stderr
|
||||||
|
//!
|
||||||
|
//! # Context
|
||||||
|
//!
|
||||||
|
//! The `Context` type is used to keep state between REPL calls. Here's an example:
|
||||||
|
//! ```rust,no_run
|
||||||
|
#![doc = include_str!("../examples/with_context.rs")]
|
||||||
|
//! ```
|
||||||
|
//! A few things to note:
|
||||||
|
//! - you pass in the initial value for your Context struct to the call to
|
||||||
|
//! [Repl::new()](struct.Repl.html#method.new)
|
||||||
|
//! - the context is passed to your command callback functions as a mutable reference
|
||||||
|
//! - the prompt can be changed after each executed commmand using with_on_after_command as shown
|
||||||
|
//!
|
||||||
|
//! # Async Support
|
||||||
|
//!
|
||||||
|
//! The `async` feature allows you to write async REPL code:
|
||||||
|
//! ```rust,no_run
|
||||||
|
#![doc = include_str!("../examples/async.rs")]
|
||||||
|
//! ```
|
||||||
|
//! A few things to note:
|
||||||
|
//! - The ugly Pin::Box workaround is required because of unstable rust async Fn's
|
||||||
|
//!
|
||||||
|
//! # Keybindings
|
||||||
|
//!
|
||||||
|
//! Per default Emacs-style keybindings are used
|
||||||
|
//! ```rust,no_run
|
||||||
|
#![doc = include_str!("../examples/custom_keybinding.rs")]
|
||||||
|
//! ```
|
||||||
|
//! A few things to note:
|
||||||
|
//! - The ugly Pin::Box workaround is required because of unstable rust async Fn's
|
||||||
|
//!
|
||||||
|
//! # Help
|
||||||
|
//! reedline-repl-rs automatically builds help commands for your REPL using clap [print_help](https://docs.rs/clap/latest/clap/struct.App.html#method.print_help):
|
||||||
|
//!
|
||||||
|
//! ```bash
|
||||||
|
//! % myapp
|
||||||
|
//! MyApp> 〉help
|
||||||
|
//! MyApp v0.1.0: My very cool app
|
||||||
|
//!
|
||||||
|
//! COMMANDS:
|
||||||
|
//! append Append name to end of list
|
||||||
|
//! help Print this message or the help of the given subcommand(s)
|
||||||
|
//! prepend Prepend name to front of list
|
||||||
|
//!
|
||||||
|
//! MyApp> 〉help append
|
||||||
|
//! append
|
||||||
|
//! Append name to end of list
|
||||||
|
//!
|
||||||
|
//! USAGE:
|
||||||
|
//! append <name>
|
||||||
|
//!
|
||||||
|
//! ARGS:
|
||||||
|
//! <name>
|
||||||
|
//!
|
||||||
|
//! OPTIONS:
|
||||||
|
//! -h, --help Print help information
|
||||||
|
//! MyApp> 〉
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Errors
|
||||||
|
//!
|
||||||
|
//! Your command functions don't need to return `reedline_repl_rs::Error`; you can return any error from
|
||||||
|
//! them. Your error will need to implement `std::fmt::Display`, so the Repl can print the error,
|
||||||
|
//! and you'll need to implement `std::convert::From` for `reedline_repl_rs::Error` to your error type.
|
||||||
|
//! This makes error handling in your command functions easier, since you can just allow whatever
|
||||||
|
//! errors your functions emit bubble up.
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
#![doc = include_str!("../examples/custom_error.rs")]
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
mod command;
|
||||||
|
mod completer;
|
||||||
|
mod error;
|
||||||
|
mod prompt;
|
||||||
|
mod repl;
|
||||||
|
|
||||||
|
pub use clap;
|
||||||
|
use clap::ArgMatches;
|
||||||
|
pub use crossterm;
|
||||||
|
pub use error::{Error, Result};
|
||||||
|
pub use nu_ansi_term;
|
||||||
|
pub use reedline;
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use repl::Repl;
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
use std::{future::Future, pin::Pin};
|
||||||
|
pub use yansi;
|
||||||
|
use yansi::Paint;
|
||||||
|
|
||||||
|
/// Command callback function signature
|
||||||
|
pub type Callback<Context, Error> =
|
||||||
|
fn(ArgMatches, &mut Context) -> std::result::Result<Option<String>, Error>;
|
||||||
|
|
||||||
|
/// Async Command callback function signature
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
pub type AsyncCallback<Context, Error> =
|
||||||
|
fn(
|
||||||
|
ArgMatches,
|
||||||
|
&'_ mut Context,
|
||||||
|
) -> Pin<Box<dyn Future<Output = std::result::Result<Option<String>, Error>> + '_>>;
|
||||||
|
|
||||||
|
/// AfterCommand callback function signature
|
||||||
|
pub type AfterCommandCallback<Context, Error> =
|
||||||
|
fn(&mut Context) -> std::result::Result<Option<String>, Error>;
|
||||||
|
|
||||||
|
/// Async AfterCommand callback function signature
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
pub type AsyncAfterCommandCallback<Context, Error> =
|
||||||
|
fn(
|
||||||
|
&'_ mut Context,
|
||||||
|
) -> Pin<Box<dyn Future<Output = std::result::Result<Option<String>, Error>> + '_>>;
|
||||||
|
|
||||||
|
/// Utility to format prompt strings as green and bold. Use yansi directly instead for custom colors.
|
||||||
|
pub fn paint_green_bold(input: &str) -> String {
|
||||||
|
Box::new(Paint::green(input).bold()).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Utility to format prompt strings as yellow and bold. Use yansi directly instead for custom colors.
|
||||||
|
pub fn paint_yellow_bold(input: &str) -> String {
|
||||||
|
Box::new(Paint::yellow(input).bold()).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the name, version and description of the Repl from your
|
||||||
|
/// crate name, version and description
|
||||||
|
#[macro_export]
|
||||||
|
#[cfg(feature = "macro")]
|
||||||
|
macro_rules! initialize_repl {
|
||||||
|
($context: expr) => {{
|
||||||
|
let repl = Repl::new($context)
|
||||||
|
.with_name(clap::crate_name!())
|
||||||
|
.with_version(clap::crate_version!())
|
||||||
|
.with_description(clap::crate_description!());
|
||||||
|
|
||||||
|
repl
|
||||||
|
}};
|
||||||
|
}
|
57
reedline-repl-rs/src/prompt.rs
Normal file
57
reedline-repl-rs/src/prompt.rs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
use reedline::{DefaultPrompt, Prompt, PromptEditMode, PromptHistorySearch};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ReplPrompt {
|
||||||
|
default: DefaultPrompt,
|
||||||
|
prefix: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Prompt for ReplPrompt {
|
||||||
|
/// Use prefix as render prompt
|
||||||
|
fn render_prompt_left(&self) -> Cow<str> {
|
||||||
|
{
|
||||||
|
Cow::Borrowed(&self.prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// call default impl
|
||||||
|
fn render_prompt_right(&self) -> Cow<str> {
|
||||||
|
// self.default.render_prompt_right()
|
||||||
|
Cow::Borrowed("")
|
||||||
|
}
|
||||||
|
fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow<str> {
|
||||||
|
self.default.render_prompt_indicator(edit_mode)
|
||||||
|
}
|
||||||
|
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
|
||||||
|
self.default.render_prompt_multiline_indicator()
|
||||||
|
}
|
||||||
|
fn render_prompt_history_search_indicator(
|
||||||
|
&self,
|
||||||
|
history_search: PromptHistorySearch,
|
||||||
|
) -> Cow<str> {
|
||||||
|
self.default
|
||||||
|
.render_prompt_history_search_indicator(history_search)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ReplPrompt {
|
||||||
|
fn default() -> Self {
|
||||||
|
ReplPrompt::new("repl")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReplPrompt {
|
||||||
|
/// Constructor for the default prompt, which takes the amount of spaces required between the left and right-hand sides of the prompt
|
||||||
|
pub fn new(left_prompt: &str) -> ReplPrompt {
|
||||||
|
ReplPrompt {
|
||||||
|
prefix: left_prompt.to_string(),
|
||||||
|
default: DefaultPrompt::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn update_prefix(&mut self, prefix: &str) {
|
||||||
|
self.prefix = prefix.to_string();
|
||||||
|
}
|
||||||
|
}
|
615
reedline-repl-rs/src/repl.rs
Normal file
615
reedline-repl-rs/src/repl.rs
Normal file
@ -0,0 +1,615 @@
|
|||||||
|
use crate::command::ReplCommand;
|
||||||
|
use crate::completer::ReplCompleter;
|
||||||
|
use crate::error::*;
|
||||||
|
use crate::prompt::ReplPrompt;
|
||||||
|
use crate::{paint_green_bold, paint_yellow_bold, AfterCommandCallback, Callback};
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
use crate::{AsyncAfterCommandCallback, AsyncCallback};
|
||||||
|
use clap::Command;
|
||||||
|
use crossterm::event::{KeyCode, KeyModifiers};
|
||||||
|
use nu_ansi_term::{Color, Style};
|
||||||
|
use reedline::{
|
||||||
|
default_emacs_keybindings, ColumnarMenu, DefaultHinter, DefaultValidator, Emacs,
|
||||||
|
ExampleHighlighter, FileBackedHistory, Keybindings, Reedline, ReedlineEvent, ReedlineMenu,
|
||||||
|
Signal,
|
||||||
|
};
|
||||||
|
use std::boxed::Box;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
type ErrorHandler<Context, E> = fn(error: E, repl: &Repl<Context, E>) -> Result<()>;
|
||||||
|
|
||||||
|
fn default_error_handler<Context, E: Display>(error: E, _repl: &Repl<Context, E>) -> Result<()> {
|
||||||
|
eprintln!("{}", error);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main REPL struct
|
||||||
|
pub struct Repl<Context, E: Display> {
|
||||||
|
name: String,
|
||||||
|
banner: Option<String>,
|
||||||
|
version: String,
|
||||||
|
description: String,
|
||||||
|
prompt: ReplPrompt,
|
||||||
|
after_command_callback: Option<AfterCommandCallback<Context, E>>,
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
after_command_callback_async: Option<AsyncAfterCommandCallback<Context, E>>,
|
||||||
|
commands: HashMap<String, ReplCommand<Context, E>>,
|
||||||
|
history: Option<PathBuf>,
|
||||||
|
history_capacity: Option<usize>,
|
||||||
|
context: Context,
|
||||||
|
keybindings: Keybindings,
|
||||||
|
hinter_style: Style,
|
||||||
|
hinter_enabled: bool,
|
||||||
|
quick_completions: bool,
|
||||||
|
partial_completions: bool,
|
||||||
|
stop_on_ctrl_c: bool,
|
||||||
|
stop_on_ctrl_d: bool,
|
||||||
|
error_handler: ErrorHandler<Context, E>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Context, E> Repl<Context, E>
|
||||||
|
where
|
||||||
|
E: Display + From<Error> + std::fmt::Debug,
|
||||||
|
{
|
||||||
|
/// Create a new Repl with the given context's initial value.
|
||||||
|
pub fn new(context: Context) -> Self {
|
||||||
|
let name = String::from("repl");
|
||||||
|
let style = Style::new().italic().fg(Color::LightGray);
|
||||||
|
let mut keybindings = default_emacs_keybindings();
|
||||||
|
keybindings.add_binding(
|
||||||
|
KeyModifiers::NONE,
|
||||||
|
KeyCode::Tab,
|
||||||
|
ReedlineEvent::Menu("completion_menu".to_string()),
|
||||||
|
);
|
||||||
|
let prompt = ReplPrompt::new(&paint_green_bold(&format!("{}> ", name)));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
banner: None,
|
||||||
|
version: String::new(),
|
||||||
|
description: String::new(),
|
||||||
|
commands: HashMap::new(),
|
||||||
|
history: None,
|
||||||
|
history_capacity: None,
|
||||||
|
after_command_callback: None,
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
after_command_callback_async: None,
|
||||||
|
quick_completions: true,
|
||||||
|
partial_completions: false,
|
||||||
|
hinter_enabled: true,
|
||||||
|
hinter_style: style,
|
||||||
|
prompt,
|
||||||
|
context,
|
||||||
|
keybindings,
|
||||||
|
stop_on_ctrl_c: false,
|
||||||
|
stop_on_ctrl_d: true,
|
||||||
|
error_handler: default_error_handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Give your Repl a name. This is used in the help summary for the Repl.
|
||||||
|
pub fn with_name(mut self, name: &str) -> Self {
|
||||||
|
self.name = name.to_string();
|
||||||
|
self.with_formatted_prompt(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Give your Repl a banner. This is printed at the start of running the Repl.
|
||||||
|
pub fn with_banner(mut self, banner: &str) -> Self {
|
||||||
|
self.banner = Some(banner.to_string());
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Give your Repl a version. This is used in the help summary for the Repl.
|
||||||
|
pub fn with_version(mut self, version: &str) -> Self {
|
||||||
|
self.version = version.to_string();
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Give your Repl a description. This is used in the help summary for the Repl.
|
||||||
|
pub fn with_description(mut self, description: &str) -> Self {
|
||||||
|
self.description = description.to_string();
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Give your REPL a callback which is called after every command and may update the prompt
|
||||||
|
pub fn with_on_after_command(mut self, callback: AfterCommandCallback<Context, E>) -> Self {
|
||||||
|
self.after_command_callback = Some(callback);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Give your REPL a callback which is called after every command and may update the prompt
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
pub fn with_on_after_command_async(
|
||||||
|
mut self,
|
||||||
|
callback: AsyncAfterCommandCallback<Context, E>,
|
||||||
|
) -> Self {
|
||||||
|
self.after_command_callback_async = Some(callback);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Give your Repl a file based history saved at history_path
|
||||||
|
pub fn with_history(mut self, history_path: PathBuf, capacity: usize) -> Self {
|
||||||
|
self.history = Some(history_path);
|
||||||
|
self.history_capacity = Some(capacity);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Give your Repl a custom prompt. The default prompt is the Repl name, followed by
|
||||||
|
/// a `>`, all in green and bold, followed by a space:
|
||||||
|
///
|
||||||
|
/// &Paint::green(format!("{}> ", name)).bold().to_string()
|
||||||
|
pub fn with_prompt(mut self, prompt: &str) -> Self {
|
||||||
|
self.prompt.update_prefix(prompt);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Give your Repl a custom prompt while applying green/bold formatting automatically
|
||||||
|
///
|
||||||
|
/// &Paint::green(format!("{}> ", name)).bold().to_string()
|
||||||
|
pub fn with_formatted_prompt(mut self, prompt: &str) -> Self {
|
||||||
|
self.prompt.update_prefix(prompt);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass in a custom error handler. This is really only for testing - the default
|
||||||
|
/// error handler simply prints the error to stderr and then returns
|
||||||
|
pub fn with_error_handler(mut self, handler: ErrorHandler<Context, E>) -> Self {
|
||||||
|
self.error_handler = handler;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turn on/off if REPL run is stopped on CTRG+C (Default: false)
|
||||||
|
pub fn with_stop_on_ctrl_c(mut self, stop_on_ctrl_c: bool) -> Self {
|
||||||
|
self.stop_on_ctrl_c = stop_on_ctrl_c;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turn on/off if REPL run is stopped on CTRG+D (Default: true)
|
||||||
|
pub fn with_stop_on_ctrl_d(mut self, stop_on_ctrl_d: bool) -> Self {
|
||||||
|
self.stop_on_ctrl_d = stop_on_ctrl_d;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turn on quick completions. These completions will auto-select if the completer
|
||||||
|
/// ever narrows down to a single entry.
|
||||||
|
pub fn with_quick_completions(mut self, quick_completions: bool) -> Self {
|
||||||
|
self.quick_completions = quick_completions;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turn on partial completions. These completions will fill the buffer with the
|
||||||
|
/// smallest common string from all the options
|
||||||
|
pub fn with_partial_completions(mut self, partial_completions: bool) -> Self {
|
||||||
|
self.partial_completions = partial_completions;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the style for reedline's fish-style history autosuggestions
|
||||||
|
///
|
||||||
|
/// Default: `nu_ansi_term::Style::new().italic().fg(nu_ansi_term::Color::LightGray)`
|
||||||
|
///
|
||||||
|
pub fn with_hinter_style(mut self, style: Style) -> Self {
|
||||||
|
self.hinter_style = style;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disables reedline's fish-style history autosuggestions
|
||||||
|
pub fn with_hinter_disabled(mut self) -> Self {
|
||||||
|
self.hinter_enabled = false;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a reedline keybinding
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If `comamnd` is an empty [`ReedlineEvent::UntilFound`]
|
||||||
|
pub fn with_keybinding(
|
||||||
|
mut self,
|
||||||
|
modifier: KeyModifiers,
|
||||||
|
key_code: KeyCode,
|
||||||
|
command: ReedlineEvent,
|
||||||
|
) -> Self {
|
||||||
|
self.keybindings.add_binding(modifier, key_code, command);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find a keybinding based on the modifier and keycode
|
||||||
|
pub fn find_keybinding(
|
||||||
|
&self,
|
||||||
|
modifier: KeyModifiers,
|
||||||
|
key_code: KeyCode,
|
||||||
|
) -> Option<ReedlineEvent> {
|
||||||
|
self.keybindings.find_binding(modifier, key_code)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get assigned keybindings
|
||||||
|
pub fn get_keybindings(&self) -> HashMap<(KeyModifiers, KeyCode), ReedlineEvent> {
|
||||||
|
// keybindings.get_keybindings() cannot be returned directly because KeyCombination is not visible
|
||||||
|
self.keybindings
|
||||||
|
.get_keybindings()
|
||||||
|
.iter()
|
||||||
|
.map(|(key, value)| ((key.modifier, key.key_code), value.clone()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a keybinding
|
||||||
|
///
|
||||||
|
/// Returns `Some(ReedlineEvent)` if the keycombination was previously bound to a particular [`ReedlineEvent`]
|
||||||
|
pub fn without_keybinding(mut self, modifier: KeyModifiers, key_code: KeyCode) -> Self {
|
||||||
|
self.keybindings.remove_binding(modifier, key_code);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a command to your REPL
|
||||||
|
pub fn with_command(
|
||||||
|
mut self,
|
||||||
|
command: Command<'static>,
|
||||||
|
callback: Callback<Context, E>,
|
||||||
|
) -> Self {
|
||||||
|
let name = command.get_name().to_string();
|
||||||
|
self.commands
|
||||||
|
.insert(name.clone(), ReplCommand::new(&name, command, callback));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a command to your REPL
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
pub fn with_command_async(
|
||||||
|
mut self,
|
||||||
|
command: Command<'static>,
|
||||||
|
callback: AsyncCallback<Context, E>,
|
||||||
|
) -> Self {
|
||||||
|
let name = command.get_name().to_string();
|
||||||
|
self.commands.insert(
|
||||||
|
name.clone(),
|
||||||
|
ReplCommand::new_async(&name, command, callback),
|
||||||
|
);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_help(&self, args: &[&str]) -> Result<()> {
|
||||||
|
if args.is_empty() {
|
||||||
|
let mut app = Command::new("app");
|
||||||
|
|
||||||
|
for (_, com) in self.commands.iter() {
|
||||||
|
app = app.subcommand(com.command.clone());
|
||||||
|
}
|
||||||
|
let mut help_bytes: Vec<u8> = Vec::new();
|
||||||
|
app.write_help(&mut help_bytes)
|
||||||
|
.expect("failed to print help");
|
||||||
|
let mut help_string =
|
||||||
|
String::from_utf8(help_bytes).expect("Help message was invalid UTF8");
|
||||||
|
let marker = "SUBCOMMANDS:";
|
||||||
|
if let Some(marker_pos) = help_string.find(marker) {
|
||||||
|
help_string = paint_yellow_bold("COMMANDS:")
|
||||||
|
+ &help_string[(marker_pos + marker.len())..help_string.len()];
|
||||||
|
}
|
||||||
|
let header = format!(
|
||||||
|
"{} {}\n{}\n",
|
||||||
|
paint_green_bold(&self.name),
|
||||||
|
self.version,
|
||||||
|
self.description
|
||||||
|
);
|
||||||
|
println!("{}", header);
|
||||||
|
println!("{}", help_string);
|
||||||
|
} else if let Some((_, subcommand)) = self
|
||||||
|
.commands
|
||||||
|
.iter()
|
||||||
|
.find(|(name, _)| name.as_str() == args[0])
|
||||||
|
{
|
||||||
|
subcommand
|
||||||
|
.command
|
||||||
|
.clone()
|
||||||
|
.print_help()
|
||||||
|
.expect("failed to print help");
|
||||||
|
println!();
|
||||||
|
} else {
|
||||||
|
eprintln!("Help not found for command '{}'", args[0]);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_command(&mut self, command: &str, args: &[&str]) -> core::result::Result<(), E> {
|
||||||
|
match self.commands.get(command) {
|
||||||
|
Some(definition) => {
|
||||||
|
let mut argv: Vec<&str> = vec![command];
|
||||||
|
argv.extend(args);
|
||||||
|
match definition.command.clone().try_get_matches_from_mut(argv) {
|
||||||
|
Ok(matches) => match (definition
|
||||||
|
.callback
|
||||||
|
.expect("Must be filled for sync commands"))(
|
||||||
|
matches, &mut self.context
|
||||||
|
) {
|
||||||
|
Ok(Some(value)) => println!("{}", value),
|
||||||
|
Ok(None) => (),
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
err.print().expect("failed to print");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.execute_after_command_callback()?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if command == "help" {
|
||||||
|
self.show_help(args)?;
|
||||||
|
} else {
|
||||||
|
return Err(Error::UnknownCommand(command.to_string()).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_after_command_callback(&mut self) -> core::result::Result<(), E> {
|
||||||
|
if let Some(callback) = self.after_command_callback {
|
||||||
|
match callback(&mut self.context) {
|
||||||
|
Ok(Some(new_prompt)) => {
|
||||||
|
self.prompt.update_prefix(&new_prompt);
|
||||||
|
}
|
||||||
|
Ok(None) => {}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("failed to execute after_command_callback {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
async fn execute_after_command_callback_async(&mut self) -> core::result::Result<(), E> {
|
||||||
|
self.execute_after_command_callback()?;
|
||||||
|
if let Some(callback) = self.after_command_callback_async {
|
||||||
|
match callback(&mut self.context).await {
|
||||||
|
Ok(new_prompt) => {
|
||||||
|
if let Some(new_prompt) = new_prompt {
|
||||||
|
self.prompt.update_prefix(&new_prompt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("failed to execute after_command_callback {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
async fn handle_command_async(
|
||||||
|
&mut self,
|
||||||
|
command: &str,
|
||||||
|
args: &[&str],
|
||||||
|
) -> core::result::Result<(), E> {
|
||||||
|
match self.commands.get(command) {
|
||||||
|
Some(definition) => {
|
||||||
|
let mut argv: Vec<&str> = vec![command];
|
||||||
|
argv.extend(args);
|
||||||
|
match definition.command.clone().try_get_matches_from_mut(argv) {
|
||||||
|
Ok(matches) => match if let Some(async_callback) = definition.async_callback {
|
||||||
|
async_callback(matches, &mut self.context).await
|
||||||
|
} else {
|
||||||
|
definition
|
||||||
|
.callback
|
||||||
|
.expect("Either async or sync callback must be set")(
|
||||||
|
matches,
|
||||||
|
&mut self.context,
|
||||||
|
)
|
||||||
|
} {
|
||||||
|
Ok(Some(value)) => println!("{}", value),
|
||||||
|
Ok(None) => (),
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
err.print().expect("failed to print");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.execute_after_command_callback_async().await?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if command == "help" {
|
||||||
|
self.show_help(args)?;
|
||||||
|
} else {
|
||||||
|
return Err(Error::UnknownCommand(command.to_string()).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_line(&self, line: &str) -> (String, Vec<String>) {
|
||||||
|
let r = regex::Regex::new(r#"("[^"\n]+"|[\S]+)"#).unwrap();
|
||||||
|
let mut args = r
|
||||||
|
.captures_iter(line)
|
||||||
|
.map(|a| a[0].to_string().replace('\"', ""))
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
let command: String = args.drain(..1).collect();
|
||||||
|
(command, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_line(&mut self, line: String) -> core::result::Result<(), E> {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if !trimmed.is_empty() {
|
||||||
|
let (command, args) = self.parse_line(trimmed);
|
||||||
|
let args = args.iter().fold(vec![], |mut state, a| {
|
||||||
|
state.push(a.as_str());
|
||||||
|
state
|
||||||
|
});
|
||||||
|
self.handle_command(&command, &args)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
async fn process_line_async(&mut self, line: String) -> core::result::Result<(), E> {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if !trimmed.is_empty() {
|
||||||
|
let (command, args) = self.parse_line(trimmed);
|
||||||
|
let args = args.iter().fold(vec![], |mut state, a| {
|
||||||
|
state.push(a.as_str());
|
||||||
|
state
|
||||||
|
});
|
||||||
|
self.handle_command_async(&command, &args).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_line_editor(&mut self) -> Result<Reedline> {
|
||||||
|
let mut valid_commands: Vec<String> = self
|
||||||
|
.commands
|
||||||
|
.iter()
|
||||||
|
.map(|(_, command)| command.name.clone())
|
||||||
|
.collect();
|
||||||
|
valid_commands.push("help".to_string());
|
||||||
|
let completer = Box::new(ReplCompleter::new(&self.commands));
|
||||||
|
let completion_menu = Box::new(ColumnarMenu::default().with_name("completion_menu"));
|
||||||
|
let validator = Box::new(DefaultValidator);
|
||||||
|
let mut line_editor = Reedline::create()
|
||||||
|
.with_edit_mode(Box::new(Emacs::new(self.keybindings.clone())))
|
||||||
|
.with_completer(completer)
|
||||||
|
.with_menu(ReedlineMenu::EngineCompleter(completion_menu))
|
||||||
|
.with_highlighter(Box::new(ExampleHighlighter::new(valid_commands.clone())))
|
||||||
|
.with_validator(validator)
|
||||||
|
.with_partial_completions(self.partial_completions)
|
||||||
|
.with_quick_completions(self.quick_completions);
|
||||||
|
|
||||||
|
if self.hinter_enabled {
|
||||||
|
line_editor = line_editor.with_hinter(Box::new(
|
||||||
|
DefaultHinter::default().with_style(self.hinter_style),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(history_path) = &self.history {
|
||||||
|
let capacity = self.history_capacity.unwrap();
|
||||||
|
let history =
|
||||||
|
FileBackedHistory::with_file(capacity, history_path.to_path_buf()).unwrap();
|
||||||
|
line_editor = line_editor.with_history(Box::new(history));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(line_editor)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute REPL
|
||||||
|
pub fn run(&mut self) -> Result<()> {
|
||||||
|
enable_virtual_terminal_processing();
|
||||||
|
if let Some(banner) = &self.banner {
|
||||||
|
println!("{}", banner);
|
||||||
|
}
|
||||||
|
let mut line_editor = self.build_line_editor()?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let sig = line_editor
|
||||||
|
.read_line(&self.prompt)
|
||||||
|
.expect("failed to read_line");
|
||||||
|
match sig {
|
||||||
|
Signal::Success(line) => {
|
||||||
|
if let Err(err) = self.process_line(line) {
|
||||||
|
(self.error_handler)(err, self)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Signal::CtrlC => {
|
||||||
|
if self.stop_on_ctrl_c {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Signal::CtrlD => {
|
||||||
|
if self.stop_on_ctrl_d {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
disable_virtual_terminal_processing();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute REPL
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
pub async fn run_async(&mut self) -> Result<()> {
|
||||||
|
enable_virtual_terminal_processing();
|
||||||
|
if let Some(banner) = &self.banner {
|
||||||
|
println!("{}", banner);
|
||||||
|
}
|
||||||
|
let mut line_editor = self.build_line_editor()?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let sig = line_editor
|
||||||
|
.read_line(&self.prompt)
|
||||||
|
.expect("failed to read_line");
|
||||||
|
match sig {
|
||||||
|
Signal::Success(line) => {
|
||||||
|
if let Err(err) = self.process_line_async(line).await {
|
||||||
|
(self.error_handler)(err, self)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Signal::CtrlC => {
|
||||||
|
if self.stop_on_ctrl_c {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Signal::CtrlD => {
|
||||||
|
if self.stop_on_ctrl_d {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
disable_virtual_terminal_processing();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn enable_virtual_terminal_processing() {
|
||||||
|
use winapi_util::console::Console;
|
||||||
|
if let Ok(mut term) = Console::stdout() {
|
||||||
|
let _guard = term.set_virtual_terminal_processing(true);
|
||||||
|
}
|
||||||
|
if let Ok(mut term) = Console::stderr() {
|
||||||
|
let _guard = term.set_virtual_terminal_processing(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn disable_virtual_terminal_processing() {
|
||||||
|
use winapi_util::console::Console;
|
||||||
|
if let Ok(mut term) = Console::stdout() {
|
||||||
|
let _guard = term.set_virtual_terminal_processing(false);
|
||||||
|
}
|
||||||
|
if let Ok(mut term) = Console::stderr() {
|
||||||
|
let _guard = term.set_virtual_terminal_processing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
pub fn enable_virtual_terminal_processing() {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
pub fn disable_virtual_terminal_processing() {
|
||||||
|
// no-op
|
||||||
|
}
|
78
rom.68k
Normal file
78
rom.68k
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
.long 0, start
|
||||||
|
start:
|
||||||
|
move.w #fakestack, a7
|
||||||
|
move.b #0x1, d0 ; Find the ROM card
|
||||||
|
bra.b find_first_card
|
||||||
|
romfindret:
|
||||||
|
move.l a0, a6 ; Save the ROM card IO base in a6 for later
|
||||||
|
lea (0xEE, a6), a7 ; Set up the stack at the end of the ROM's IO space RAM
|
||||||
|
bsr.b find_largest_ram ; Find the largest RAM card and put the IO base in a5
|
||||||
|
move.l a0, a5
|
||||||
|
move.l d0, d7
|
||||||
|
move.b #0x4, d0 ; Find a storage card and put the IO base in a4
|
||||||
|
bsr.b find_first_card
|
||||||
|
move.l a0, a4
|
||||||
|
; Transfer the bootsector load code to the ROM's
|
||||||
|
; built in RAM at the start of it's IO space
|
||||||
|
move.w #(ramcode_end - ramcode), d0 ; Put the length of the ramcode in d0
|
||||||
|
move.w #ramcode, a0 ; Put the address of the ramcode in a0
|
||||||
|
move.l a6, a1 ; Put the start of the ROM's IO space RAM in a0
|
||||||
|
ramcode_loop:
|
||||||
|
move.b (a0)+, (a1)+ ; Transfer a byte of ramcode to the ROM's IO space RAM
|
||||||
|
dbra d0, ramcode_loop ; Loop back if there is more to transfer
|
||||||
|
jmp (a6) ; Jump to the ramcode
|
||||||
|
stop #0x2700
|
||||||
|
|
||||||
|
ramcode:
|
||||||
|
move.b #0x0, (0xFE, a6) ; Disable the ROM
|
||||||
|
move.l #0x1, (a5) ; Enable the RAM at base 0x0
|
||||||
|
move.l d7, a7
|
||||||
|
; Load sector 0 to 0x0
|
||||||
|
move.l #0x0, (a4) ; Set the sector number to 0
|
||||||
|
; Transfer 0x100 (256) bytes from the storage card's data register to 0x0
|
||||||
|
move.w #0x0, a1 ; Load a1, the destination address register, with 0x0
|
||||||
|
move.w #0xFF, d0 ; Load d0 with the sector size - 1.
|
||||||
|
sector_loop:
|
||||||
|
move.b (4, a4), (a1)+ ; Transfer a byte of sector data to the destination
|
||||||
|
dbra d0, sector_loop ; Loop back if there is more to transfer
|
||||||
|
jmp (0x0).W ; Jump to the loaded sector
|
||||||
|
|
||||||
|
stop #0x2700
|
||||||
|
ramcode_end:
|
||||||
|
|
||||||
|
; Finds the first card with the type in d0.b, and returns it's IO base address in a0, or 0 if not found
|
||||||
|
; Clobbers d1
|
||||||
|
find_first_card:
|
||||||
|
move.l #0xff0000, a0 ; a0 holds the address of the current card
|
||||||
|
ffc_loop:
|
||||||
|
lea (0x100,a0), a0 ; adda.l #$100,a0 ; Move to the next card
|
||||||
|
move.b (0xff, a0), d1 ; Load the type of the card into d1
|
||||||
|
beq.b ffc_done ; If the type is 0 (empty slot), we have scanned all cards, so exit the loop
|
||||||
|
cmp.b d0, d1 ; If the card is the type we want, return with the address in a0
|
||||||
|
beq.b ffc_done
|
||||||
|
bra.b ffc_loop ; Loop back and check the next card
|
||||||
|
ffc_done:
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Finds the largest RAM card, and returns it's IO base address in a0 and size in d0, or 0 if not found
|
||||||
|
; Clobbers d1, a1
|
||||||
|
find_largest_ram:
|
||||||
|
move.l #0x0, d0 ; d0 holds the size of the largest RAM card found
|
||||||
|
move.w #0x0, a0 ; a0 holds the address of the largest RAM card found
|
||||||
|
move.l #0xff0000, a1 ; a1 holds the address of the current card
|
||||||
|
flr_loop:
|
||||||
|
lea (0x100,a1), a1 ; adda.l #$100,a0 ; Move to the next card
|
||||||
|
move.b (0xff, a1), d1 ; Load the type of the card into d1
|
||||||
|
beq.b flr_done ; If the type is 0 (empty slot), we have scanned all cards, so exit the loop
|
||||||
|
cmp.b #0x2, d1 ; If the card isn't a RAM card, skip it
|
||||||
|
bne.b flr_loop
|
||||||
|
move.l (0x4, a1), d1 ; Load the card's size into d1
|
||||||
|
cmp.l d0, d1 ; If the current size is less than the largest size found, go back to the start of the loop
|
||||||
|
ble.b flr_loop
|
||||||
|
move.l d1, d0 ; Store the sise and address of the new latgest card in s0 and a0
|
||||||
|
move.l a1, a0
|
||||||
|
bra.b flr_loop ; Loop back and check the next card
|
||||||
|
flr_done:
|
||||||
|
rts
|
||||||
|
fakestack:
|
||||||
|
.long romfindret
|
116
rom.lst
Normal file
116
rom.lst
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
Sections:
|
||||||
|
00: ".text" (0-AC)
|
||||||
|
|
||||||
|
|
||||||
|
Source: "rom.68k"
|
||||||
|
00:00000000 00000000 1: .long 0, start
|
||||||
|
00:00000004 00000008
|
||||||
|
2: start:
|
||||||
|
00:00000008 3E7C00A8 3: move.w #fakestack, a7
|
||||||
|
00:0000000C 103C0001 4: move.b #0x1, d0 ; Find the ROM card
|
||||||
|
00:00000010 6054 5: bra.b find_first_card
|
||||||
|
6: romfindret:
|
||||||
|
00:00000012 2C48 7: move.l a0, a6 ; Save the ROM card IO base in a6 for later
|
||||||
|
00:00000014 4FEE00EE 8: lea (0xEE, a6), a7 ; Set up the stack at the end of the ROM's IO space RAM
|
||||||
|
00:00000018 6164 9: bsr.b find_largest_ram ; Find the largest RAM card and put the IO base in a5
|
||||||
|
00:0000001A 2A48 10: move.l a0, a5
|
||||||
|
00:0000001C 2E00 11: move.l d0, d7
|
||||||
|
00:0000001E 103C0004 12: move.b #0x4, d0 ; Find a storage card and put the IO base in a4
|
||||||
|
00:00000022 6142 13: bsr.b find_first_card
|
||||||
|
00:00000024 2848 14: move.l a0, a4
|
||||||
|
15: ; Transfer the bootsector load code to the ROM's
|
||||||
|
16: ; built in RAM at the start of it's IO space
|
||||||
|
00:00000026 303C002A 17: move.w #(ramcode_end - ramcode), d0 ; Put the length of the ramcode in d0
|
||||||
|
00:0000002A 307C003C 18: move.w #ramcode, a0 ; Put the address of the ramcode in a0
|
||||||
|
00:0000002E 224E 19: move.l a6, a1 ; Put the start of the ROM's IO space RAM in a0
|
||||||
|
20: ramcode_loop:
|
||||||
|
00:00000030 12D8 21: move.b (a0)+, (a1)+ ; Transfer a byte of ramcode to the ROM's IO space RAM
|
||||||
|
00:00000032 51C8FFFC 22: dbra d0, ramcode_loop ; Loop back if there is more to transfer
|
||||||
|
00:00000036 4ED6 23: jmp (a6) ; Jump to the ramcode
|
||||||
|
00:00000038 4E722700 24: stop #0x2700
|
||||||
|
25:
|
||||||
|
26: ramcode:
|
||||||
|
00:0000003C 1D7C000000FE 27: move.b #0x0, (0xFE, a6) ; Disable the ROM
|
||||||
|
00:00000042 2ABC00000001 28: move.l #0x1, (a5) ; Enable the RAM at base 0x0
|
||||||
|
00:00000048 2E47 29: move.l d7, a7
|
||||||
|
30: ; Load sector 0 to 0x0
|
||||||
|
00:0000004A 28BC00000000 31: move.l #0x0, (a4) ; Set the sector number to 0
|
||||||
|
32: ; Transfer 0x100 (256) bytes from the storage card's data register to 0x0
|
||||||
|
00:00000050 93C9 33: move.w #0x0, a1 ; Load a1, the destination address register, with 0x0
|
||||||
|
00:00000052 303C00FF 34: move.w #0xFF, d0 ; Load d0 with the sector size - 1.
|
||||||
|
35: sector_loop:
|
||||||
|
00:00000056 12EC0004 36: move.b (4, a4), (a1)+ ; Transfer a byte of sector data to the destination
|
||||||
|
00:0000005A 51C8FFFA 37: dbra d0, sector_loop ; Loop back if there is more to transfer
|
||||||
|
00:0000005E 4EF80000 38: jmp (0x0).W ; Jump to the loaded sector
|
||||||
|
39:
|
||||||
|
00:00000062 4E722700 40: stop #0x2700
|
||||||
|
41: ramcode_end:
|
||||||
|
42:
|
||||||
|
43: ; Finds the first card with the type in d0.b, and returns it's IO base address in a0, or 0 if not found
|
||||||
|
44: ; Clobbers d1
|
||||||
|
45: find_first_card:
|
||||||
|
00:00000066 207C00FF0000 46: move.l #0xff0000, a0 ; a0 holds the address of the current card
|
||||||
|
47: ffc_loop:
|
||||||
|
00:0000006C 41E80100 48: lea (0x100,a0), a0 ; adda.l #$100,a0 ; Move to the next card
|
||||||
|
00:00000070 122800FF 49: move.b (0xff, a0), d1 ; Load the type of the card into d1
|
||||||
|
00:00000074 6706 50: beq.b ffc_done ; If the type is 0 (empty slot), we have scanned all cards, so exit the loop
|
||||||
|
00:00000076 B200 51: cmp.b d0, d1 ; If the card is the type we want, return with the address in a0
|
||||||
|
00:00000078 6702 52: beq.b ffc_done
|
||||||
|
00:0000007A 60F0 53: bra.b ffc_loop ; Loop back and check the next card
|
||||||
|
54: ffc_done:
|
||||||
|
00:0000007C 4E75 55: rts
|
||||||
|
56:
|
||||||
|
57: ; Finds the largest RAM card, and returns it's IO base address in a0 and size in d0, or 0 if not found
|
||||||
|
58: ; Clobbers d1, a1
|
||||||
|
59: find_largest_ram:
|
||||||
|
00:0000007E 7000 60: move.l #0x0, d0 ; d0 holds the size of the largest RAM card found
|
||||||
|
00:00000080 91C8 61: move.w #0x0, a0 ; a0 holds the address of the largest RAM card found
|
||||||
|
00:00000082 227C00FF0000 62: move.l #0xff0000, a1 ; a1 holds the address of the current card
|
||||||
|
63: flr_loop:
|
||||||
|
00:00000088 43E90100 64: lea (0x100,a1), a1 ; adda.l #$100,a0 ; Move to the next card
|
||||||
|
00:0000008C 122900FF 65: move.b (0xff, a1), d1 ; Load the type of the card into d1
|
||||||
|
00:00000090 6714 66: beq.b flr_done ; If the type is 0 (empty slot), we have scanned all cards, so exit the loop
|
||||||
|
00:00000092 B23C0002 67: cmp.b #0x2, d1 ; If the card isn't a RAM card, skip it
|
||||||
|
00:00000096 66F0 68: bne.b flr_loop
|
||||||
|
00:00000098 22290004 69: move.l (0x4, a1), d1 ; Load the card's size into d1
|
||||||
|
00:0000009C B280 70: cmp.l d0, d1 ; If the current size is less than the largest size found, go back to the start of the loop
|
||||||
|
00:0000009E 6FE8 71: ble.b flr_loop
|
||||||
|
00:000000A0 2001 72: move.l d1, d0 ; Store the sise and address of the new latgest card in s0 and a0
|
||||||
|
00:000000A2 2049 73: move.l a1, a0
|
||||||
|
00:000000A4 60E2 74: bra.b flr_loop ; Loop back and check the next card
|
||||||
|
75: flr_done:
|
||||||
|
00:000000A6 4E75 76: rts
|
||||||
|
77: fakestack:
|
||||||
|
00:000000A8 00000012 78: .long romfindret
|
||||||
|
79:
|
||||||
|
|
||||||
|
|
||||||
|
Symbols by name:
|
||||||
|
fakestack 00:000000A8
|
||||||
|
ffc_done 00:0000007C
|
||||||
|
ffc_loop 00:0000006C
|
||||||
|
find_first_card 00:00000066
|
||||||
|
find_largest_ram 00:0000007E
|
||||||
|
flr_done 00:000000A6
|
||||||
|
flr_loop 00:00000088
|
||||||
|
ramcode 00:0000003C
|
||||||
|
ramcode_end 00:00000066
|
||||||
|
ramcode_loop 00:00000030
|
||||||
|
romfindret 00:00000012
|
||||||
|
sector_loop 00:00000056
|
||||||
|
start 00:00000008
|
||||||
|
|
||||||
|
Symbols by value:
|
||||||
|
00000008 start
|
||||||
|
00000012 romfindret
|
||||||
|
00000030 ramcode_loop
|
||||||
|
0000003C ramcode
|
||||||
|
00000056 sector_loop
|
||||||
|
00000066 find_first_card
|
||||||
|
00000066 ramcode_end
|
||||||
|
0000006C ffc_loop
|
||||||
|
0000007C ffc_done
|
||||||
|
0000007E find_largest_ram
|
||||||
|
00000088 flr_loop
|
||||||
|
000000A6 flr_done
|
||||||
|
000000A8 fakestack
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
133
src/backplane.rs
Normal file
133
src/backplane.rs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use nullable_result::GeneralIterExt;
|
||||||
|
use serde_yaml::Mapping;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
card::{Card, CardType},
|
||||||
|
m68k::BusError,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Backplane {
|
||||||
|
cards: Vec<Box<dyn Card>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum CardAddError {
|
||||||
|
BackplaneFull,
|
||||||
|
InvalidType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CardAddError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::BackplaneFull => f.write_str("Backplane full, could not add card"),
|
||||||
|
Self::InvalidType => f.write_str("Invalid card type"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Backplane {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { cards: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn cards(&self) -> &[Box<dyn Card>] {
|
||||||
|
self.cards.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cards_mut(&mut self) -> &mut Vec<Box<dyn Card>> {
|
||||||
|
&mut self.cards
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_card(&mut self, type_name: &str, config: &Mapping) -> Result<usize, CardAddError> {
|
||||||
|
if self.cards.len() >= 255 {
|
||||||
|
return Err(CardAddError::BackplaneFull);
|
||||||
|
}
|
||||||
|
self.cards.push(
|
||||||
|
inventory::iter::<CardType>()
|
||||||
|
.find(|card_type| card_type.name == type_name)
|
||||||
|
.ok_or(CardAddError::InvalidType)?
|
||||||
|
.new_card(config),
|
||||||
|
);
|
||||||
|
Ok(self.cards.len() - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_word(&mut self, address: u32) -> Result<u16, BusError> {
|
||||||
|
match address {
|
||||||
|
(0..=0x00fe_ffff) | (0x0100_0000..=0xFFFF_FFFF) => self
|
||||||
|
.cards
|
||||||
|
.iter_mut()
|
||||||
|
.try_find_map(|card| card.read_word(address))
|
||||||
|
.result(BusError),
|
||||||
|
(0x00ff_0000..=0x00ff_00ff) => Ok(0),
|
||||||
|
(0x00ff_0100..=0x00ff_ffff) => self
|
||||||
|
.cards
|
||||||
|
.get_mut(((address >> 8) as u8 - 1) as usize)
|
||||||
|
.map_or(Ok(0), |card| {
|
||||||
|
card.read_word_io(address as u8)
|
||||||
|
.optional_result()
|
||||||
|
.unwrap_or(Ok(0))
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_byte(&mut self, address: u32) -> Result<u8, BusError> {
|
||||||
|
match address {
|
||||||
|
(0..=0x00fe_ffff) | (0x0100_0000..=0xFFFF_FFFF) => self
|
||||||
|
.cards
|
||||||
|
.iter_mut()
|
||||||
|
.try_find_map(|card| card.read_byte(address))
|
||||||
|
.result(BusError),
|
||||||
|
(0x00ff_0000..=0x00ff_00ff) => Ok(0),
|
||||||
|
(0x00ff_0100..=0x00ff_ffff) => self
|
||||||
|
.cards
|
||||||
|
.get_mut(((address >> 8) as u8 - 1) as usize)
|
||||||
|
.map_or(Ok(0), |card| {
|
||||||
|
card.read_byte_io(address as u8)
|
||||||
|
.optional_result()
|
||||||
|
.unwrap_or(Ok(0))
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_word(&mut self, address: u32, data: u16) -> Result<(), BusError> {
|
||||||
|
match address {
|
||||||
|
(0..=0x00fe_ffff) | (0x0100_0000..=0xFFFF_FFFF) => self
|
||||||
|
.cards
|
||||||
|
.iter_mut()
|
||||||
|
.try_find_map(|card| card.write_word(address, data))
|
||||||
|
.result(BusError),
|
||||||
|
(0x00ff_0000..=0x00ff_00ff) => Ok(()),
|
||||||
|
(0x00ff_0100..=0x00ff_ffff) => self
|
||||||
|
.cards
|
||||||
|
.get_mut(((address >> 8) as u8 - 1) as usize)
|
||||||
|
.map_or(Ok(()), |card| {
|
||||||
|
card.write_word_io(address as u8, data)
|
||||||
|
.optional_result()
|
||||||
|
.unwrap_or(Ok(()))
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_byte(&mut self, address: u32, data: u8) -> Result<(), BusError> {
|
||||||
|
match address {
|
||||||
|
(0..=0x00fe_ffff) | (0x0100_0000..=0xFFFF_FFFF) => self
|
||||||
|
.cards
|
||||||
|
.iter_mut()
|
||||||
|
.try_find_map(|card| card.write_byte(address, data))
|
||||||
|
.result(BusError),
|
||||||
|
(0x00ff_0000..=0x00ff_00ff) => Ok(()),
|
||||||
|
(0x00ff_0100..=0x00ff_ffff) => self
|
||||||
|
.cards
|
||||||
|
.get_mut(((address >> 8) as u8 - 1) as usize)
|
||||||
|
.map_or(Ok(()), |card| {
|
||||||
|
card.write_byte_io(address as u8, data)
|
||||||
|
.optional_result()
|
||||||
|
.unwrap_or(Ok(()))
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
130
src/card.rs
Normal file
130
src/card.rs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
use crate::m68k::BusError;
|
||||||
|
use nullable_result::NullableResult;
|
||||||
|
use serde_yaml::Mapping;
|
||||||
|
use std::fmt::{Debug, Display};
|
||||||
|
|
||||||
|
pub struct CardType {
|
||||||
|
pub name: &'static str,
|
||||||
|
new: fn(data: &Mapping) -> Box<dyn Card>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CardType {
|
||||||
|
pub const fn new<T: Card + 'static>(name: &'static str) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
new: T::new_dyn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_card(&self, data: &Mapping) -> Box<dyn Card> {
|
||||||
|
(self.new)(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inventory::collect!(CardType);
|
||||||
|
|
||||||
|
pub trait Card: Debug + Display {
|
||||||
|
fn new(data: &Mapping) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
fn new_dyn(data: &Mapping) -> Box<dyn Card>
|
||||||
|
where
|
||||||
|
Self: Sized + 'static,
|
||||||
|
{
|
||||||
|
Box::new(Self::new(data))
|
||||||
|
}
|
||||||
|
fn display(&self) -> String {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
fn read_byte(&mut self, _address: u32) -> NullableResult<u8, BusError> {
|
||||||
|
NullableResult::Null
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_byte(&mut self, _address: u32, _data: u8) -> NullableResult<(), BusError> {
|
||||||
|
NullableResult::Null
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_byte_io(&mut self, _address: u8) -> NullableResult<u8, BusError> {
|
||||||
|
NullableResult::Null
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_byte_io(&mut self, _address: u8, _data: u8) -> NullableResult<(), BusError> {
|
||||||
|
NullableResult::Null
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_word(&mut self, address: u32) -> NullableResult<u16, BusError> {
|
||||||
|
assert!((address & 0x1) == 0);
|
||||||
|
let upper_byte = self.read_byte(address)?;
|
||||||
|
let lower_byte = self.read_byte(address + 1)?;
|
||||||
|
NullableResult::Ok((u16::from(upper_byte) << 8) | u16::from(lower_byte))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_word(&mut self, address: u32, data: u16) -> NullableResult<(), BusError> {
|
||||||
|
assert!((address & 0x1) == 0);
|
||||||
|
self.write_byte(address, (data >> 8) as u8)?;
|
||||||
|
self.write_byte(address + 1, data as u8)?;
|
||||||
|
NullableResult::Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_word_io(&mut self, address: u8) -> NullableResult<u16, BusError> {
|
||||||
|
assert!((address & 0x1) == 0);
|
||||||
|
let upper_byte = self.read_byte_io(address)?;
|
||||||
|
let lower_byte = self.read_byte_io(address + 1)?;
|
||||||
|
NullableResult::Ok((u16::from(upper_byte) << 8) | u16::from(lower_byte))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_word_io(&mut self, address: u8, data: u16) -> NullableResult<(), BusError> {
|
||||||
|
assert!((address & 0x1) == 0);
|
||||||
|
self.write_byte_io(address, (data >> 8) as u8)?;
|
||||||
|
self.write_byte_io(address + 1, data as u8)?;
|
||||||
|
NullableResult::Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd(&mut self, _cmd: &[&str]) {}
|
||||||
|
fn reset(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const fn u64_set_be_byte(val: u64, idx: u8, byte: u8) -> u64 {
|
||||||
|
let mut bytes = val.to_be_bytes();
|
||||||
|
bytes[idx as usize] = byte;
|
||||||
|
u64::from_be_bytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const fn u64_get_be_byte(val: u64, idx: u8) -> u8 {
|
||||||
|
val.to_be_bytes()[idx as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const fn u32_set_be_byte(val: u32, idx: u8, byte: u8) -> u32 {
|
||||||
|
let mut bytes = val.to_be_bytes();
|
||||||
|
bytes[idx as usize] = byte;
|
||||||
|
u32::from_be_bytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const fn u32_get_be_byte(val: u32, idx: u8) -> u8 {
|
||||||
|
val.to_be_bytes()[idx as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const fn u16_set_be_byte(val: u16, idx: u8, byte: u8) -> u16 {
|
||||||
|
let mut bytes = val.to_be_bytes();
|
||||||
|
bytes[idx as usize] = byte;
|
||||||
|
u16::from_be_bytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const fn u16_get_be_byte(val: u16, idx: u8) -> u8 {
|
||||||
|
val.to_be_bytes()[idx as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! register {
|
||||||
|
($typ: ty, $name: literal) => {
|
||||||
|
paste::paste! {
|
||||||
|
inventory::submit!($crate::card::CardType::new::<$typ>($name));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
943
src/disas.rs
Normal file
943
src/disas.rs
Normal file
@ -0,0 +1,943 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use bitvec::prelude::*;
|
||||||
|
|
||||||
|
use crate::instruction::{
|
||||||
|
BitInsType, Condition, EffectiveAddress, Instruction, MoveDirection, RegisterEffective,
|
||||||
|
Rotation, ShiftDirection, ShiftType, Size,
|
||||||
|
};
|
||||||
|
|
||||||
|
use derive_try_from_primitive::TryFromPrimitive;
|
||||||
|
|
||||||
|
#[derive(TryFromPrimitive, Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
enum InstructionCategory {
|
||||||
|
BitMovepImmed = 0,
|
||||||
|
MoveByte = 1,
|
||||||
|
MoveLong = 2,
|
||||||
|
MoveWord = 3,
|
||||||
|
Misc = 4,
|
||||||
|
AddqSubqSccDbcc = 5,
|
||||||
|
Branch = 6,
|
||||||
|
Moveq = 7,
|
||||||
|
OrDivSbcd = 8,
|
||||||
|
SubSubx = 9,
|
||||||
|
CmpEor = 11,
|
||||||
|
AndMulAbcdExg = 12,
|
||||||
|
AddAddx = 13,
|
||||||
|
ShiftRotate = 14,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(TryFromPrimitive, Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
enum AddressingMode {
|
||||||
|
DataReg = 0,
|
||||||
|
AddressReg = 1,
|
||||||
|
Address = 2,
|
||||||
|
AdddresPostinc = 3,
|
||||||
|
AddressPredec = 4,
|
||||||
|
AddressDisplacement = 5,
|
||||||
|
AddressIndex = 6,
|
||||||
|
Misc = 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(TryFromPrimitive, Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
enum MiscMode {
|
||||||
|
PcDisplacement = 2,
|
||||||
|
PcIndex = 3,
|
||||||
|
AbsoluteShort = 0,
|
||||||
|
AbsoluteLong = 1,
|
||||||
|
Immediate = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disasm<T>(
|
||||||
|
pc: u32,
|
||||||
|
byte_read: &mut dyn FnMut(u32) -> Result<u8, T>,
|
||||||
|
) -> Result<(Instruction, u32), DisassemblyError<T>> {
|
||||||
|
let mut disasm = Disasm { pc, byte_read };
|
||||||
|
Ok((disasm.disasm()?, disasm.pc))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DisassemblyError<T> {
|
||||||
|
InvalidInstruction,
|
||||||
|
ReadError(u32, T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Display> Display for DisassemblyError<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::InvalidInstruction => f.write_str("Invalid instruction"),
|
||||||
|
Self::ReadError(addr, e) => f.write_fmt(format_args!("Read error at {} ({})", addr, e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Disasm<'a, T> {
|
||||||
|
pc: u32,
|
||||||
|
byte_read: &'a mut dyn FnMut(u32) -> Result<u8, T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Disasm<'_, T> {
|
||||||
|
fn disasm(&mut self) -> Result<Instruction, DisassemblyError<T>> {
|
||||||
|
let ins_word = self.read_prog_word()?;
|
||||||
|
let ins_word = ins_word.view_bits::<Msb0>();
|
||||||
|
let category = InstructionCategory::try_from(ins_word[0..4].load_be::<u8>())
|
||||||
|
.map_err(|_| DisassemblyError::InvalidInstruction)?;
|
||||||
|
let size: Size = match category {
|
||||||
|
InstructionCategory::BitMovepImmed => {
|
||||||
|
if ins_word[7] {
|
||||||
|
if ins_word[10..13].load_be::<u8>() == 1 {
|
||||||
|
if ins_word[9] {
|
||||||
|
Size::Long
|
||||||
|
} else {
|
||||||
|
Size::Word
|
||||||
|
}
|
||||||
|
} else if ins_word[10..13].load_be::<u8>() == 0 {
|
||||||
|
Size::Long
|
||||||
|
} else {
|
||||||
|
Size::Byte
|
||||||
|
}
|
||||||
|
} else if ins_word[4..7].load_be::<u8>() <= 6 {
|
||||||
|
match ins_word[8..10].load_be::<u8>() {
|
||||||
|
0 => Size::Byte,
|
||||||
|
1 => Size::Word,
|
||||||
|
2 => Size::Long,
|
||||||
|
_ => return Err(DisassemblyError::InvalidInstruction),
|
||||||
|
}
|
||||||
|
} else if ins_word[10..13].load_be::<u8>() == 0 {
|
||||||
|
Size::Long
|
||||||
|
} else {
|
||||||
|
Size::Byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InstructionCategory::MoveByte => Size::Byte,
|
||||||
|
InstructionCategory::MoveWord => Size::Word,
|
||||||
|
InstructionCategory::MoveLong | InstructionCategory::Moveq => Size::Long,
|
||||||
|
InstructionCategory::Misc => {
|
||||||
|
if ins_word[7..10].load_be::<u8>() == 7 {
|
||||||
|
Size::Long
|
||||||
|
} else if !ins_word[4] {
|
||||||
|
match ins_word[8..10].load_be::<u8>() {
|
||||||
|
0 => Size::Byte,
|
||||||
|
1 => Size::Word,
|
||||||
|
2 => Size::Long,
|
||||||
|
3 => match ins_word[5..7].load_be::<u8>() {
|
||||||
|
0 | 3 => Size::Word,
|
||||||
|
1 => return Err(DisassemblyError::InvalidInstruction),
|
||||||
|
2 => Size::Byte,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
} else if !ins_word[6] {
|
||||||
|
if ins_word[8] {
|
||||||
|
if ins_word[9] {
|
||||||
|
Size::Long
|
||||||
|
} else {
|
||||||
|
Size::Word
|
||||||
|
}
|
||||||
|
} else if !ins_word[9] {
|
||||||
|
Size::Byte
|
||||||
|
} else if ins_word[9] & (ins_word[10..13].load_be::<u8>() == 0) {
|
||||||
|
Size::Word
|
||||||
|
} else {
|
||||||
|
Size::Long
|
||||||
|
}
|
||||||
|
} else if !ins_word[5] {
|
||||||
|
match ins_word[8..10].load_be::<u8>() {
|
||||||
|
0 | 3 => Size::Byte,
|
||||||
|
1 => Size::Word,
|
||||||
|
2 => Size::Long,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
} else if ins_word[8] {
|
||||||
|
if ins_word[7..10].load_be::<u8>() == 7 {
|
||||||
|
Size::Long
|
||||||
|
} else if ins_word[7..10].load_be::<u8>() == 6 {
|
||||||
|
Size::Word
|
||||||
|
} else if !ins_word[6] {
|
||||||
|
if ins_word[9] {
|
||||||
|
Size::Long
|
||||||
|
} else {
|
||||||
|
Size::Word
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Size::Byte
|
||||||
|
}
|
||||||
|
} else if ins_word[9..12].load_be::<u8>() == 5 {
|
||||||
|
Size::Word
|
||||||
|
} else {
|
||||||
|
Size::Long
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InstructionCategory::AddqSubqSccDbcc => match ins_word[8..10].load_be::<u8>() {
|
||||||
|
0 => Size::Byte,
|
||||||
|
1 => Size::Word,
|
||||||
|
2 => Size::Long,
|
||||||
|
3 => match ins_word[10..13].load_be::<u8>() {
|
||||||
|
1 => Size::Word,
|
||||||
|
_ => Size::Byte,
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
InstructionCategory::Branch => {
|
||||||
|
if ins_word[8..16].load_be::<i8>() == 0 {
|
||||||
|
Size::Word
|
||||||
|
} else {
|
||||||
|
Size::Byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InstructionCategory::OrDivSbcd => {
|
||||||
|
if ins_word[10..12].load_be::<u8>() == 0 {
|
||||||
|
Size::Byte
|
||||||
|
} else {
|
||||||
|
match ins_word[8..10].load_be::<u8>() {
|
||||||
|
0 => Size::Byte,
|
||||||
|
1 | 3 => Size::Word,
|
||||||
|
2 => Size::Long,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InstructionCategory::AndMulAbcdExg => match ins_word[8..10].load_be::<u8>() {
|
||||||
|
0 => Size::Byte,
|
||||||
|
1 | 2 => Size::Long,
|
||||||
|
3 => Size::Word,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
InstructionCategory::AddAddx
|
||||||
|
| InstructionCategory::SubSubx
|
||||||
|
| InstructionCategory::CmpEor => match ins_word[8..10].load_be::<u8>() {
|
||||||
|
0 => Size::Byte,
|
||||||
|
1 => Size::Word,
|
||||||
|
2 => Size::Long,
|
||||||
|
3 => {
|
||||||
|
if ins_word[7] {
|
||||||
|
Size::Long
|
||||||
|
} else {
|
||||||
|
Size::Word
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
InstructionCategory::ShiftRotate => match ins_word[8..10].load_be::<u8>() {
|
||||||
|
0 => Size::Byte,
|
||||||
|
1 | 3 => Size::Word,
|
||||||
|
2 => Size::Long,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
match category {
|
||||||
|
InstructionCategory::BitMovepImmed => {
|
||||||
|
if ins_word[7] {
|
||||||
|
if ins_word[10..13].load_be::<u8>() == 1 {
|
||||||
|
let dir = if ins_word[8] {
|
||||||
|
MoveDirection::RegToMem
|
||||||
|
} else {
|
||||||
|
MoveDirection::MemToReg
|
||||||
|
};
|
||||||
|
let areg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dreg = ins_word[4..7].load_be::<u8>();
|
||||||
|
let start_offset = self.read_prog_word()? as i16;
|
||||||
|
Ok(Instruction::Movep {
|
||||||
|
dir,
|
||||||
|
size,
|
||||||
|
areg,
|
||||||
|
dreg,
|
||||||
|
start_offset,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
let idx = EffectiveAddress::DataReg(ins_word[4..7].load_be::<u8>());
|
||||||
|
let typ = match ins_word[8..10].load_be::<u8>() {
|
||||||
|
0 => BitInsType::Test,
|
||||||
|
1 => BitInsType::Change,
|
||||||
|
2 => BitInsType::Clear,
|
||||||
|
3 => BitInsType::Set,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
Ok(Instruction::Bit {
|
||||||
|
typ,
|
||||||
|
idx,
|
||||||
|
dst,
|
||||||
|
size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match ins_word[4..7].load_be::<u8>() {
|
||||||
|
0 => {
|
||||||
|
let imm = self.read_immediate(size)?;
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
if (dst_mode == AddressingMode::Misc) & (dst_reg == 0b100) {
|
||||||
|
if size == Size::Byte {
|
||||||
|
Ok(Instruction::OriCcr(imm as u8))
|
||||||
|
} else {
|
||||||
|
Ok(Instruction::OriSr(imm as u16))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
Ok(Instruction::Ori(size, dst, imm))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
let imm = self.read_immediate(size)?;
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
if (dst_mode == AddressingMode::Misc) & (dst_reg == 0b100) {
|
||||||
|
if size == Size::Byte {
|
||||||
|
Ok(Instruction::AndiCcr(imm as u8))
|
||||||
|
} else {
|
||||||
|
Ok(Instruction::AndiSr(imm as u16))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
Ok(Instruction::Andi(size, dst, imm))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
let imm = self.read_immediate(size)?;
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
Ok(Instruction::Subi(size, dst, imm))
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
let imm = self.read_immediate(size)?;
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
Ok(Instruction::Addi(size, dst, imm))
|
||||||
|
}
|
||||||
|
4 => {
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
let idx = self.read_immediate(Size::Byte)?;
|
||||||
|
let typ = match ins_word[8..10].load_be::<u8>() {
|
||||||
|
0 => BitInsType::Test,
|
||||||
|
1 => BitInsType::Change,
|
||||||
|
2 => BitInsType::Clear,
|
||||||
|
3 => BitInsType::Set,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
Ok(Instruction::Bit {
|
||||||
|
typ,
|
||||||
|
idx: EffectiveAddress::Immediate(idx),
|
||||||
|
dst,
|
||||||
|
size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
5 => {
|
||||||
|
let imm = self.read_immediate(size)?;
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
if (dst_mode == AddressingMode::Misc) & (dst_reg == 0b100) {
|
||||||
|
if size == Size::Byte {
|
||||||
|
Ok(Instruction::EoriCcr(imm as u8))
|
||||||
|
} else {
|
||||||
|
Ok(Instruction::EoriSr(imm as u16))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
Ok(Instruction::Eori(size, dst, imm))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
6 => {
|
||||||
|
let imm = self.read_immediate(size)?;
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
Ok(Instruction::Cmpi(size, dst, imm))
|
||||||
|
}
|
||||||
|
7 => Err(DisassemblyError::InvalidInstruction),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InstructionCategory::MoveByte
|
||||||
|
| InstructionCategory::MoveWord
|
||||||
|
| InstructionCategory::MoveLong => {
|
||||||
|
let src_mode = AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let src_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let src = self.decode_effective(src_mode, src_reg, size)?;
|
||||||
|
let dst_mode = AddressingMode::try_from(ins_word[7..10].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[4..7].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
Ok(Instruction::Move { src, dst, size })
|
||||||
|
}
|
||||||
|
InstructionCategory::Misc => {
|
||||||
|
if ins_word[4..10].load_be::<u8>() == 0b00_0011 {
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
Ok(Instruction::MoveFromSr(dst))
|
||||||
|
} else if ins_word[4..10].load_be::<u8>() == 0b01_0011 {
|
||||||
|
let src_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let src_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let src = self.decode_effective(src_mode, src_reg, size)?;
|
||||||
|
Ok(Instruction::MoveToCcr(src))
|
||||||
|
} else if ins_word[4..10].load_be::<u8>() == 0b01_1011 {
|
||||||
|
let src_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let src_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let src = self.decode_effective(src_mode, src_reg, size)?;
|
||||||
|
Ok(Instruction::MoveToSr(src))
|
||||||
|
} else if ins_word[4..8].load_be::<u8>() == 0b0000 {
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
Ok(Instruction::Negx(size, dst))
|
||||||
|
} else if ins_word[4..8].load_be::<u8>() == 0b0010 {
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
Ok(Instruction::Clr(size, dst))
|
||||||
|
} else if ins_word[4..8].load_be::<u8>() == 0b0100 {
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
Ok(Instruction::Neg(size, dst))
|
||||||
|
} else if ins_word[4..8].load_be::<u8>() == 0b0110 {
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
Ok(Instruction::Not(size, dst))
|
||||||
|
} else if ins_word[4..9].load_be::<u8>() == 0b10001
|
||||||
|
&& ins_word[10..13].load_be::<u8>() == 0
|
||||||
|
{
|
||||||
|
let dst = ins_word[13..16].load_be::<u8>();
|
||||||
|
Ok(Instruction::Ext(size, dst))
|
||||||
|
} else if ins_word[4..10].load_be::<u8>() == 0b10_0000 {
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
Ok(Instruction::Nbcd(dst))
|
||||||
|
} else if ins_word[4..10].load_be::<u8>() == 0b10_0001 {
|
||||||
|
if ins_word[10..13].load_be::<u8>() == 0 {
|
||||||
|
let dst = ins_word[13..16].load_be::<u8>();
|
||||||
|
Ok(Instruction::Swap(dst))
|
||||||
|
} else {
|
||||||
|
let src_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let src_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let src = self.decode_effective(src_mode, src_reg, Size::Byte)?;
|
||||||
|
Ok(Instruction::Pea(src))
|
||||||
|
}
|
||||||
|
} else if ins_word[4..10].load_be::<u8>() == 0b10_1011 {
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, Size::Byte)?;
|
||||||
|
Ok(Instruction::Tas(dst))
|
||||||
|
} else if ins_word[4..8].load_be::<u8>() == 0b1010 {
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, Size::Byte)?;
|
||||||
|
Ok(Instruction::Tst(size, dst))
|
||||||
|
} else if ins_word[4..12].load_be::<u16>() == 0b1110_0100 {
|
||||||
|
let vector = ins_word[12..16].load_be::<u8>() + 32;
|
||||||
|
Ok(Instruction::Trap(vector))
|
||||||
|
} else if ins_word[4..13].load_be::<u16>() == 0b1_1100_1010 {
|
||||||
|
let areg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let displacement = self.read_immediate(Size::Word)? as u16;
|
||||||
|
Ok(Instruction::Link { areg, displacement })
|
||||||
|
} else if ins_word[4..13].load_be::<u16>() == 0b1_1100_1011 {
|
||||||
|
let dst = ins_word[13..16].load_be::<u8>();
|
||||||
|
Ok(Instruction::Unlk(dst))
|
||||||
|
} else if ins_word[4..12].load_be::<u8>() == 0b1110_0110 {
|
||||||
|
let areg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dir = if ins_word[12] {
|
||||||
|
MoveDirection::MemToReg
|
||||||
|
} else {
|
||||||
|
MoveDirection::RegToMem
|
||||||
|
};
|
||||||
|
Ok(Instruction::MoveUsp(dir, areg))
|
||||||
|
} else if ins_word[4..16].load_be::<u16>() == 0b1110_0111_0000 {
|
||||||
|
Ok(Instruction::Reset)
|
||||||
|
} else if ins_word[4..16].load_be::<u16>() == 0b1110_0111_0001 {
|
||||||
|
Ok(Instruction::Nop)
|
||||||
|
} else if ins_word[4..16].load_be::<u16>() == 0b1110_0111_0010 {
|
||||||
|
let sr = self.read_immediate(Size::Word)? as u16;
|
||||||
|
Ok(Instruction::Stop(sr))
|
||||||
|
} else if ins_word[4..16].load_be::<u16>() == 0b1110_0111_0011 {
|
||||||
|
Ok(Instruction::Rte)
|
||||||
|
} else if ins_word[4..16].load_be::<u16>() == 0b1110_0111_0101 {
|
||||||
|
Ok(Instruction::Rts)
|
||||||
|
} else if ins_word[4..16].load_be::<u16>() == 0b1110_0111_0110 {
|
||||||
|
Ok(Instruction::Trapv)
|
||||||
|
} else if ins_word[4..16].load_be::<u16>() == 0b1110_0111_0111 {
|
||||||
|
Ok(Instruction::Rtr)
|
||||||
|
} else if ins_word[4..10].load_be::<u8>() == 0b11_1010 {
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, Size::Long)?;
|
||||||
|
Ok(Instruction::Jsr(dst))
|
||||||
|
} else if ins_word[4..10].load_be::<u8>() == 0b11_1011 {
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, Size::Long)?;
|
||||||
|
Ok(Instruction::Jmp(dst))
|
||||||
|
} else if ins_word[4] && ins_word[6..9].load_be::<u8>() == 0b001 {
|
||||||
|
let dir = if ins_word[5] {
|
||||||
|
MoveDirection::MemToReg
|
||||||
|
} else {
|
||||||
|
MoveDirection::RegToMem
|
||||||
|
};
|
||||||
|
let mode = AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let ea = self.decode_effective(mode, reg, Size::Byte)?;
|
||||||
|
let reg_mask = self.read_prog_word()?;
|
||||||
|
let reg_mask = BitSlice::<u16, Lsb0>::from_element(®_mask);
|
||||||
|
let mut regs: Vec<EffectiveAddress> = Vec::new();
|
||||||
|
if mode == AddressingMode::AddressPredec {
|
||||||
|
for (i, bit) in reg_mask.iter().enumerate() {
|
||||||
|
if *bit {
|
||||||
|
if i < 8 {
|
||||||
|
let reg_no = 7 - i;
|
||||||
|
regs.push(EffectiveAddress::AddressReg(reg_no as u8));
|
||||||
|
} else {
|
||||||
|
let reg_no = 7 - (i - 8);
|
||||||
|
regs.push(EffectiveAddress::DataReg(reg_no as u8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (i, bit) in reg_mask.iter().enumerate() {
|
||||||
|
if *bit {
|
||||||
|
if i < 8 {
|
||||||
|
regs.push(EffectiveAddress::DataReg(i as u8));
|
||||||
|
} else {
|
||||||
|
regs.push(EffectiveAddress::AddressReg((i - 8) as u8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Instruction::Movem(dir, size, ea, regs))
|
||||||
|
} else if ins_word[7..10].load_be::<u8>() == 0b111 {
|
||||||
|
let src_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let src_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let src = self.decode_effective(src_mode, src_reg, Size::Byte)?;
|
||||||
|
let areg = ins_word[4..7].load_be::<u8>();
|
||||||
|
Ok(Instruction::Lea(areg, src))
|
||||||
|
} else if ins_word[7..10].load_be::<u8>() == 0b110 {
|
||||||
|
let upper_bound_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let upper_bound_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let upper_bound =
|
||||||
|
self.decode_effective(upper_bound_mode, upper_bound_reg, Size::Byte)?;
|
||||||
|
let dreg = ins_word[4..7].load_be::<u8>();
|
||||||
|
Ok(Instruction::Chk(dreg, upper_bound))
|
||||||
|
} else {
|
||||||
|
Err(DisassemblyError::InvalidInstruction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InstructionCategory::AddqSubqSccDbcc => {
|
||||||
|
if ins_word[8..10].load_be::<u8>() == 3 {
|
||||||
|
let condition = Condition::try_from(ins_word[4..8].load_be::<u8>()).unwrap();
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
if ins_word[10..13].load_be::<u8>() == 1 {
|
||||||
|
let displacement = self.read_immediate(Size::Word)? as i16;
|
||||||
|
let pc = self.pc.wrapping_sub(2);
|
||||||
|
let new_pc = if displacement >= 0 {
|
||||||
|
pc.wrapping_add(displacement as u32)
|
||||||
|
} else {
|
||||||
|
pc.wrapping_sub(-displacement as u32)
|
||||||
|
};
|
||||||
|
Ok(Instruction::Dbcc(condition, dst_reg, displacement, new_pc))
|
||||||
|
} else {
|
||||||
|
Ok(Instruction::Scc(condition, dst))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
let src = ins_word[4..7].load_be::<u8>() as i8;
|
||||||
|
if ins_word[7] {
|
||||||
|
Ok(Instruction::Subq(size, src, dst))
|
||||||
|
} else {
|
||||||
|
Ok(Instruction::Addq(size, src, dst))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InstructionCategory::Branch => {
|
||||||
|
let condition = Condition::try_from(ins_word[4..8].load_be::<u8>()).unwrap();
|
||||||
|
let displacement = match size {
|
||||||
|
Size::Word => self.read_immediate(size)? as i16,
|
||||||
|
Size::Byte => i16::from(ins_word[8..16].load_be::<i8>()),
|
||||||
|
Size::Long => unreachable!(),
|
||||||
|
};
|
||||||
|
let pc = match size {
|
||||||
|
Size::Word => self.pc - 2,
|
||||||
|
Size::Byte => self.pc,
|
||||||
|
Size::Long => unreachable!(),
|
||||||
|
};
|
||||||
|
let new_pc = if displacement >= 0 {
|
||||||
|
pc.wrapping_add(displacement as u16 as u32)
|
||||||
|
} else {
|
||||||
|
pc.wrapping_sub(-displacement as u16 as u32)
|
||||||
|
};
|
||||||
|
if condition == Condition::False {
|
||||||
|
Ok(Instruction::Bsr(size, displacement, new_pc))
|
||||||
|
} else if condition == Condition::True {
|
||||||
|
Ok(Instruction::Bra(size, displacement, new_pc))
|
||||||
|
} else {
|
||||||
|
Ok(Instruction::Bcc(size, condition, displacement, new_pc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InstructionCategory::Moveq => {
|
||||||
|
let dreg = ins_word[4..7].load_be::<u8>();
|
||||||
|
let imm = ins_word[8..16].load_be::<u8>() as i8;
|
||||||
|
Ok(Instruction::Moveq { dreg, imm })
|
||||||
|
}
|
||||||
|
InstructionCategory::OrDivSbcd =>
|
||||||
|
{
|
||||||
|
#[allow(clippy::if_same_then_else)]
|
||||||
|
if ins_word[8..10].load_be::<u8>() == 3 {
|
||||||
|
let src_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let src_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let src = self.decode_effective(src_mode, src_reg, size)?;
|
||||||
|
let dst = ins_word[4..7].load_be::<u8>();
|
||||||
|
if ins_word[7] {
|
||||||
|
Ok(Instruction::Divs(dst, src))
|
||||||
|
} else {
|
||||||
|
Ok(Instruction::Divu(dst, src))
|
||||||
|
}
|
||||||
|
} else if ins_word[7..12].load_be::<u8>() == 0b1_0000 {
|
||||||
|
let mode = if ins_word[12] {
|
||||||
|
AddressingMode::AddressPredec
|
||||||
|
} else {
|
||||||
|
AddressingMode::DataReg
|
||||||
|
};
|
||||||
|
let src_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let src = self.decode_effective(mode, src_reg, size)?;
|
||||||
|
let dst_reg = ins_word[4..7].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(mode, dst_reg, size)?;
|
||||||
|
Ok(Instruction::Sbcd { src, dst })
|
||||||
|
} else {
|
||||||
|
let dreg = EffectiveAddress::DataReg(ins_word[4..7].load_be::<u8>());
|
||||||
|
let ea_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let ea_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let ea = self.decode_effective(ea_mode, ea_reg, size)?;
|
||||||
|
let (src, dst) = if ins_word[7] { (dreg, ea) } else { (ea, dreg) };
|
||||||
|
Ok(Instruction::Or { size, src, dst })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InstructionCategory::SubSubx => {
|
||||||
|
let src;
|
||||||
|
let dst;
|
||||||
|
if ins_word[8..10].load_be::<u8>() == 3 {
|
||||||
|
let src = self.decode_effective(
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap(),
|
||||||
|
ins_word[13..16].load_be::<u8>(),
|
||||||
|
size,
|
||||||
|
)?;
|
||||||
|
let areg = ins_word[4..7].load_be::<u8>();
|
||||||
|
Ok(Instruction::Suba(areg, size, src))
|
||||||
|
} else {
|
||||||
|
let dir = ins_word[7];
|
||||||
|
if dir & (ins_word[10..12].load_be::<u8>() == 0) {
|
||||||
|
let mode = if ins_word[12] {
|
||||||
|
AddressingMode::AddressPredec
|
||||||
|
} else {
|
||||||
|
AddressingMode::DataReg
|
||||||
|
};
|
||||||
|
src =
|
||||||
|
self.decode_effective(mode, ins_word[13..16].load_be::<u8>(), size)?;
|
||||||
|
dst = self.decode_effective(mode, ins_word[4..7].load_be::<u8>(), size)?;
|
||||||
|
Ok(Instruction::Subx { size, src, dst })
|
||||||
|
} else {
|
||||||
|
let am_1 =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let reg_1 = ins_word[13..16].load_be::<u8>();
|
||||||
|
let ea_1 = self.decode_effective(am_1, reg_1, size)?;
|
||||||
|
let dreg = EffectiveAddress::DataReg(ins_word[4..7].load_be::<u8>());
|
||||||
|
if dir {
|
||||||
|
Ok(Instruction::Sub {
|
||||||
|
size,
|
||||||
|
src: dreg,
|
||||||
|
dst: ea_1,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(Instruction::Sub {
|
||||||
|
size,
|
||||||
|
src: ea_1,
|
||||||
|
dst: dreg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InstructionCategory::CmpEor => {
|
||||||
|
if ins_word[8..10].load_be::<u8>() == 3 {
|
||||||
|
let src_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let src_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let src = self.decode_effective(src_mode, src_reg, size)?;
|
||||||
|
let dst = ins_word[4..7].load_be::<u8>();
|
||||||
|
Ok(Instruction::Cmpa(dst, size, src))
|
||||||
|
} else if ins_word[7] {
|
||||||
|
#[allow(clippy::if_same_then_else)]
|
||||||
|
if ins_word[10..13].load_be::<u8>() == 1 {
|
||||||
|
let dst = ins_word[4..7].load_be::<u8>();
|
||||||
|
let src = ins_word[13..16].load_be::<u8>();
|
||||||
|
Ok(Instruction::Cmpm { size, src, dst })
|
||||||
|
} else {
|
||||||
|
let src = EffectiveAddress::DataReg(ins_word[4..7].load_be::<u8>());
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
Ok(Instruction::Eor { size, src, dst })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let src_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let src_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let src = self.decode_effective(src_mode, src_reg, size)?;
|
||||||
|
let dst = ins_word[4..7].load_be::<u8>();
|
||||||
|
Ok(Instruction::Cmp(dst, size, src))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InstructionCategory::AndMulAbcdExg => match ins_word[8..10].load_be::<u8>() {
|
||||||
|
3 => {
|
||||||
|
let src_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let src_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let src = self.decode_effective(src_mode, src_reg, size)?;
|
||||||
|
let dst = ins_word[7..10].load_be::<u8>();
|
||||||
|
if ins_word[7] {
|
||||||
|
Ok(Instruction::Muls(dst, src))
|
||||||
|
} else {
|
||||||
|
Ok(Instruction::Mulu(dst, src))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0 => {
|
||||||
|
let mode = if ins_word[12] {
|
||||||
|
AddressingMode::AddressPredec
|
||||||
|
} else {
|
||||||
|
AddressingMode::DataReg
|
||||||
|
};
|
||||||
|
let src_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let src = self.decode_effective(mode, src_reg, size)?;
|
||||||
|
let dst_reg = ins_word[4..7].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(mode, dst_reg, size)?;
|
||||||
|
Ok(Instruction::Abcd { src, dst })
|
||||||
|
}
|
||||||
|
x @ (1 | 2) => {
|
||||||
|
if ins_word[7] && ins_word[10..12].load_be::<u8>() == 0 {
|
||||||
|
let (mode1, mode2) = match x << 1 | u8::from(ins_word[12]) {
|
||||||
|
0b010 => (AddressingMode::DataReg, AddressingMode::DataReg),
|
||||||
|
0b011 => (AddressingMode::AddressReg, AddressingMode::AddressReg),
|
||||||
|
0b101 => (AddressingMode::DataReg, AddressingMode::AddressReg),
|
||||||
|
_ => return Err(DisassemblyError::InvalidInstruction),
|
||||||
|
};
|
||||||
|
let reg1 = ins_word[4..7].load_be::<u8>();
|
||||||
|
let reg2 = ins_word[13..16].load_be::<u8>();
|
||||||
|
let ea1 = self.decode_effective(mode1, reg1, Size::Long)?;
|
||||||
|
let ea2 = self.decode_effective(mode2, reg2, Size::Long)?;
|
||||||
|
Ok(Instruction::Exg { src: ea1, dst: ea2 })
|
||||||
|
} else {
|
||||||
|
let dreg = EffectiveAddress::DataReg(ins_word[4..7].load_be::<u8>());
|
||||||
|
let ea_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let ea_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let ea = self.decode_effective(ea_mode, ea_reg, size)?;
|
||||||
|
let (src, dst) = if ins_word[7] { (dreg, ea) } else { (ea, dreg) };
|
||||||
|
Ok(Instruction::And { size, src, dst })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
InstructionCategory::AddAddx => {
|
||||||
|
if ins_word[8..10].load_be::<u8>() == 3 {
|
||||||
|
let src = self.decode_effective(
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap(),
|
||||||
|
ins_word[13..16].load_be::<u8>(),
|
||||||
|
size,
|
||||||
|
)?;
|
||||||
|
let areg = ins_word[4..7].load_be::<u8>();
|
||||||
|
Ok(Instruction::Adda(areg, size, src))
|
||||||
|
} else {
|
||||||
|
let dir = ins_word[7];
|
||||||
|
if dir & (ins_word[10..12].load_be::<u8>() == 0) {
|
||||||
|
let mode = if ins_word[12] {
|
||||||
|
AddressingMode::AddressPredec
|
||||||
|
} else {
|
||||||
|
AddressingMode::DataReg
|
||||||
|
};
|
||||||
|
let src =
|
||||||
|
self.decode_effective(mode, ins_word[13..16].load_be::<u8>(), size)?;
|
||||||
|
let dst =
|
||||||
|
self.decode_effective(mode, ins_word[7..10].load_be::<u8>(), size)?;
|
||||||
|
Ok(Instruction::Addx { size, src, dst })
|
||||||
|
} else {
|
||||||
|
let am_1 =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let reg_1 = ins_word[13..16].load_be::<u8>();
|
||||||
|
let ea_1 = self.decode_effective(am_1, reg_1, size)?;
|
||||||
|
let dreg = EffectiveAddress::DataReg(ins_word[4..7].load_be::<u8>());
|
||||||
|
if dir {
|
||||||
|
Ok(Instruction::Add {
|
||||||
|
size,
|
||||||
|
src: dreg,
|
||||||
|
dst: ea_1,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(Instruction::Add {
|
||||||
|
size,
|
||||||
|
src: ea_1,
|
||||||
|
dst: dreg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InstructionCategory::ShiftRotate => {
|
||||||
|
let dir = if ins_word[7] {
|
||||||
|
ShiftDirection::Left
|
||||||
|
} else {
|
||||||
|
ShiftDirection::Right
|
||||||
|
};
|
||||||
|
let (op, dst, rotation) = if ins_word[8..10].load_be::<u8>() == 3 {
|
||||||
|
let op = ins_word[5..7].load_be::<u8>();
|
||||||
|
let dst_mode =
|
||||||
|
AddressingMode::try_from(ins_word[10..13].load_be::<u8>()).unwrap();
|
||||||
|
let dst_reg = ins_word[13..16].load_be::<u8>();
|
||||||
|
let dst = self.decode_effective(dst_mode, dst_reg, size)?;
|
||||||
|
(op, dst, Rotation::Immediate(1))
|
||||||
|
} else {
|
||||||
|
let op = ins_word[11..13].load_be::<u8>();
|
||||||
|
let dst = EffectiveAddress::DataReg(ins_word[13..16].load_be::<u8>());
|
||||||
|
let rotation = if ins_word[10] {
|
||||||
|
Rotation::Register(ins_word[4..7].load_be::<u8>())
|
||||||
|
} else {
|
||||||
|
Rotation::Immediate(ins_word[4..7].load_be::<u8>())
|
||||||
|
};
|
||||||
|
(op, dst, rotation)
|
||||||
|
};
|
||||||
|
let op = match op {
|
||||||
|
0 => ShiftType::Arithmetic,
|
||||||
|
1 => ShiftType::Logical,
|
||||||
|
2 => ShiftType::RotateExtend,
|
||||||
|
3 => ShiftType::Rotate,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
Ok(Instruction::Shift(op, size, dir, rotation, dst))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_effective(
|
||||||
|
&mut self,
|
||||||
|
mode: AddressingMode,
|
||||||
|
reg: u8,
|
||||||
|
size: Size,
|
||||||
|
) -> Result<EffectiveAddress, DisassemblyError<T>> {
|
||||||
|
if matches!(mode, AddressingMode::Misc) {
|
||||||
|
let misc_mode = MiscMode::try_from(reg).unwrap();
|
||||||
|
match misc_mode {
|
||||||
|
MiscMode::PcDisplacement => {
|
||||||
|
let pc = self.pc;
|
||||||
|
Ok(EffectiveAddress::PcDisplacement(pc, self.read_prog_word()?))
|
||||||
|
}
|
||||||
|
MiscMode::PcIndex => {
|
||||||
|
let pc = self.pc;
|
||||||
|
let (idx, idx_size, displacement) = self.read_ext_word()?;
|
||||||
|
Ok(EffectiveAddress::PcIndex(pc, displacement, idx, idx_size))
|
||||||
|
}
|
||||||
|
MiscMode::AbsoluteShort => Ok(EffectiveAddress::AbsoluteShort(i32::from(
|
||||||
|
self.read_prog_word()? as i16,
|
||||||
|
)
|
||||||
|
as u16)),
|
||||||
|
MiscMode::AbsoluteLong => Ok(EffectiveAddress::AbsoluteLong(
|
||||||
|
self.read_immediate(Size::Long)?,
|
||||||
|
)),
|
||||||
|
MiscMode::Immediate => Ok(EffectiveAddress::Immediate(self.read_immediate(size)?)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match mode {
|
||||||
|
AddressingMode::DataReg => Ok(EffectiveAddress::DataReg(reg)),
|
||||||
|
AddressingMode::AddressReg => Ok(EffectiveAddress::AddressReg(reg)),
|
||||||
|
AddressingMode::Address => Ok(EffectiveAddress::Address(reg)),
|
||||||
|
AddressingMode::AdddresPostinc => Ok(EffectiveAddress::AddressPostinc(reg)),
|
||||||
|
AddressingMode::AddressPredec => Ok(EffectiveAddress::AddressPredec(reg)),
|
||||||
|
AddressingMode::AddressDisplacement => Ok(EffectiveAddress::AddressDisplacement(
|
||||||
|
reg,
|
||||||
|
self.read_prog_word()? as i16,
|
||||||
|
)),
|
||||||
|
AddressingMode::AddressIndex => {
|
||||||
|
let (idx, idx_size, displacement) = self.read_ext_word()?;
|
||||||
|
Ok(EffectiveAddress::AddressIndex {
|
||||||
|
reg,
|
||||||
|
displacement,
|
||||||
|
idx,
|
||||||
|
idx_size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
AddressingMode::Misc => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ext_word(&mut self) -> Result<(RegisterEffective, Size, u8), DisassemblyError<T>> {
|
||||||
|
let ext_word = self.read_prog_word()?;
|
||||||
|
let ext_word = ext_word.view_bits::<Msb0>();
|
||||||
|
let mode = AddressingMode::try_from(ext_word[0..1].load_be::<u8>()).unwrap();
|
||||||
|
let reg = ext_word[1..4].load_be::<u8>();
|
||||||
|
let size = if ext_word[4] { Size::Long } else { Size::Word };
|
||||||
|
let ea = RegisterEffective::try_from(self.decode_effective(mode, reg, size)?).unwrap();
|
||||||
|
let displacement = ext_word[8..16].load_be::<u8>();
|
||||||
|
Ok((ea, size, displacement))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_prog_word(&mut self) -> Result<u16, DisassemblyError<T>> {
|
||||||
|
let word = ((self.byte_read)(self.pc)
|
||||||
|
.map_err(|e| DisassemblyError::ReadError(self.pc, e))? as u16)
|
||||||
|
<< 8
|
||||||
|
| ((self.byte_read)(self.pc + 1)
|
||||||
|
.map_err(|e| DisassemblyError::ReadError(self.pc + 1, e))? as u16);
|
||||||
|
self.pc += 2;
|
||||||
|
Ok(word)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_immediate(&mut self, size: Size) -> Result<u32, DisassemblyError<T>> {
|
||||||
|
match size {
|
||||||
|
Size::Byte => Ok(u32::from(self.read_prog_word()? & 0xFF)),
|
||||||
|
Size::Word => Ok(u32::from(self.read_prog_word()?)),
|
||||||
|
Size::Long => {
|
||||||
|
Ok((u32::from(self.read_prog_word()?) << 16) | u32::from(self.read_prog_word()?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
524
src/instruction.rs
Normal file
524
src/instruction.rs
Normal file
@ -0,0 +1,524 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use derive_try_from_primitive::TryFromPrimitive;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum RegisterEffective {
|
||||||
|
DataReg(u8),
|
||||||
|
AddressReg(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct FromEffectiveError;
|
||||||
|
|
||||||
|
impl TryFrom<EffectiveAddress> for RegisterEffective {
|
||||||
|
type Error = FromEffectiveError;
|
||||||
|
|
||||||
|
fn try_from(value: EffectiveAddress) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
EffectiveAddress::DataReg(x) => Ok(Self::DataReg(x)),
|
||||||
|
EffectiveAddress::AddressReg(x) => Ok(Self::AddressReg(x)),
|
||||||
|
_ => Err(FromEffectiveError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for RegisterEffective {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
Display::fmt(&EffectiveAddress::from(*self), f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum EffectiveAddress {
|
||||||
|
DataReg(u8),
|
||||||
|
AddressReg(u8),
|
||||||
|
Address(u8),
|
||||||
|
AddressPostinc(u8),
|
||||||
|
AddressPredec(u8),
|
||||||
|
AddressDisplacement(u8, i16),
|
||||||
|
AddressIndex {
|
||||||
|
reg: u8,
|
||||||
|
displacement: u8,
|
||||||
|
idx: RegisterEffective,
|
||||||
|
idx_size: Size,
|
||||||
|
},
|
||||||
|
PcDisplacement(u32, u16),
|
||||||
|
PcIndex(u32, u8, RegisterEffective, Size),
|
||||||
|
AbsoluteShort(u16),
|
||||||
|
AbsoluteLong(u32),
|
||||||
|
Immediate(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RegisterEffective> for EffectiveAddress {
|
||||||
|
fn from(value: RegisterEffective) -> Self {
|
||||||
|
match value {
|
||||||
|
RegisterEffective::DataReg(x) => Self::DataReg(x),
|
||||||
|
RegisterEffective::AddressReg(x) => Self::AddressReg(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for EffectiveAddress {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::DataReg(r) => f.write_fmt(format_args!("D{}", r)),
|
||||||
|
Self::AddressReg(r) => f.write_fmt(format_args!("A{}", r)),
|
||||||
|
Self::Address(r) => f.write_fmt(format_args!("(A{})", r)),
|
||||||
|
Self::AddressPostinc(r) => f.write_fmt(format_args!("(A{})+", r)),
|
||||||
|
Self::AddressPredec(r) => f.write_fmt(format_args!("-(A{})", r)),
|
||||||
|
Self::AddressDisplacement(r, d) => f.write_fmt(format_args!("({}, A{})", d, r)),
|
||||||
|
Self::AddressIndex {
|
||||||
|
reg,
|
||||||
|
displacement,
|
||||||
|
idx,
|
||||||
|
..
|
||||||
|
} => f.write_fmt(format_args!("({}, A{}, {})", displacement, reg, idx)),
|
||||||
|
Self::PcDisplacement(_, d) => f.write_fmt(format_args!("(0x{:x}, PC)", d)),
|
||||||
|
Self::PcIndex(_, d, idx, _) => f.write_fmt(format_args!("({}, PC, {})", d, idx)),
|
||||||
|
Self::AbsoluteShort(a) => f.write_fmt(format_args!("(0x{:x}).W", a)),
|
||||||
|
Self::AbsoluteLong(a) => f.write_fmt(format_args!("(0x{:x}).L", a)),
|
||||||
|
Self::Immediate(i) => f.write_fmt(format_args!("#0x{:x}", i)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Size {
|
||||||
|
Byte,
|
||||||
|
Word,
|
||||||
|
Long,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Size {
|
||||||
|
pub fn byte_size(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Self::Byte => 1,
|
||||||
|
Self::Word => 2,
|
||||||
|
Self::Long => 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Size {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Byte => f.write_str("B"),
|
||||||
|
Self::Word => f.write_str("W"),
|
||||||
|
Self::Long => f.write_str("L"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ArithType {
|
||||||
|
Reg,
|
||||||
|
Addr,
|
||||||
|
Ext,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(TryFromPrimitive, Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Condition {
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
Higher,
|
||||||
|
LowerOrSame,
|
||||||
|
CarryClear,
|
||||||
|
CarrySet,
|
||||||
|
NotEqual,
|
||||||
|
Equal,
|
||||||
|
OverflowClear,
|
||||||
|
OverflowSet,
|
||||||
|
Plus,
|
||||||
|
Minus,
|
||||||
|
GreaterOrEqual,
|
||||||
|
LessThan,
|
||||||
|
GreaterThan,
|
||||||
|
LessOrEqual,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::nonminimal_bool)] // Exact formula is preserved
|
||||||
|
impl Condition {
|
||||||
|
pub fn matches(self, cc: u8) -> bool {
|
||||||
|
let carry = (cc & 0x1) > 0;
|
||||||
|
let overflow = (cc & 0x2) > 0;
|
||||||
|
let zero = (cc & 0x4) > 0;
|
||||||
|
let negative = (cc & 0x8) > 0;
|
||||||
|
match self {
|
||||||
|
Self::True => true,
|
||||||
|
Self::False => false,
|
||||||
|
Self::Higher => !carry && !zero,
|
||||||
|
Self::LowerOrSame => carry | zero,
|
||||||
|
Self::CarryClear => !carry,
|
||||||
|
Self::CarrySet => carry,
|
||||||
|
Self::NotEqual => !zero,
|
||||||
|
Self::Equal => zero,
|
||||||
|
Self::OverflowClear => !overflow,
|
||||||
|
Self::OverflowSet => overflow,
|
||||||
|
Self::Plus => !negative,
|
||||||
|
Self::Minus => negative,
|
||||||
|
Self::GreaterOrEqual => (negative && overflow) || (!negative && !overflow),
|
||||||
|
Self::LessThan => (negative && !overflow) || (!negative && overflow),
|
||||||
|
Self::GreaterThan => {
|
||||||
|
(negative && overflow && !zero) || (!negative && !overflow && !zero)
|
||||||
|
}
|
||||||
|
Self::LessOrEqual => zero || (negative && !overflow) || (!negative && overflow),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Condition {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
Self::True => "T",
|
||||||
|
Self::False => "F",
|
||||||
|
Self::Higher => "HI",
|
||||||
|
Self::LowerOrSame => "LS",
|
||||||
|
Self::CarryClear => "CC",
|
||||||
|
Self::CarrySet => "CS",
|
||||||
|
Self::NotEqual => "NE",
|
||||||
|
Self::Equal => "EQ",
|
||||||
|
Self::OverflowClear => "VC",
|
||||||
|
Self::OverflowSet => "VS",
|
||||||
|
Self::Plus => "PL",
|
||||||
|
Self::Minus => "MI",
|
||||||
|
Self::GreaterOrEqual => "GE",
|
||||||
|
Self::LessThan => "LT",
|
||||||
|
Self::GreaterThan => "GT",
|
||||||
|
Self::LessOrEqual => "LE",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum BitInsType {
|
||||||
|
Test,
|
||||||
|
Change,
|
||||||
|
Clear,
|
||||||
|
Set,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for BitInsType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Test => write!(f, "BTST"),
|
||||||
|
Self::Change => write!(f, "BCHG"),
|
||||||
|
Self::Clear => write!(f, "BCLR"),
|
||||||
|
Self::Set => write!(f, "BSET"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum MoveDirection {
|
||||||
|
MemToReg,
|
||||||
|
RegToMem,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Rotation {
|
||||||
|
Immediate(u8),
|
||||||
|
Register(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Rotation {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Immediate(val) => write!(f, "#{val}"),
|
||||||
|
Self::Register(reg) => write!(f, "D{reg}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ShiftType {
|
||||||
|
Arithmetic,
|
||||||
|
Logical,
|
||||||
|
RotateExtend,
|
||||||
|
Rotate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ShiftType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Arithmetic => write!(f, "AS"),
|
||||||
|
Self::Logical => write!(f, "LS"),
|
||||||
|
Self::RotateExtend => write!(f, "ROX"),
|
||||||
|
Self::Rotate => write!(f, "RO"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ShiftDirection {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ShiftDirection {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Left => write!(f, "L"),
|
||||||
|
Self::Right => write!(f, "R"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Instruction {
|
||||||
|
OriCcr(u8),
|
||||||
|
OriSr(u16),
|
||||||
|
Ori(Size, EffectiveAddress, u32),
|
||||||
|
AndiCcr(u8),
|
||||||
|
AndiSr(u16),
|
||||||
|
Andi(Size, EffectiveAddress, u32),
|
||||||
|
Subi(Size, EffectiveAddress, u32),
|
||||||
|
Addi(Size, EffectiveAddress, u32),
|
||||||
|
EoriCcr(u8),
|
||||||
|
EoriSr(u16),
|
||||||
|
Eori(Size, EffectiveAddress, u32),
|
||||||
|
Cmpi(Size, EffectiveAddress, u32),
|
||||||
|
Bit {
|
||||||
|
typ: BitInsType,
|
||||||
|
idx: EffectiveAddress,
|
||||||
|
dst: EffectiveAddress,
|
||||||
|
size: Size,
|
||||||
|
},
|
||||||
|
Movep {
|
||||||
|
dir: MoveDirection,
|
||||||
|
size: Size,
|
||||||
|
dreg: u8,
|
||||||
|
areg: u8,
|
||||||
|
start_offset: i16,
|
||||||
|
},
|
||||||
|
Move {
|
||||||
|
src: EffectiveAddress,
|
||||||
|
dst: EffectiveAddress,
|
||||||
|
size: Size,
|
||||||
|
},
|
||||||
|
MoveFromSr(EffectiveAddress),
|
||||||
|
MoveToCcr(EffectiveAddress),
|
||||||
|
MoveToSr(EffectiveAddress),
|
||||||
|
Negx(Size, EffectiveAddress),
|
||||||
|
Clr(Size, EffectiveAddress),
|
||||||
|
Neg(Size, EffectiveAddress),
|
||||||
|
Not(Size, EffectiveAddress),
|
||||||
|
Ext(Size, u8),
|
||||||
|
Nbcd(EffectiveAddress),
|
||||||
|
Swap(u8),
|
||||||
|
Pea(EffectiveAddress),
|
||||||
|
Tas(EffectiveAddress),
|
||||||
|
Tst(Size, EffectiveAddress),
|
||||||
|
Trap(u8),
|
||||||
|
Link {
|
||||||
|
areg: u8,
|
||||||
|
displacement: u16,
|
||||||
|
},
|
||||||
|
Unlk(u8),
|
||||||
|
MoveUsp(MoveDirection, u8),
|
||||||
|
Reset,
|
||||||
|
Nop,
|
||||||
|
Stop(u16),
|
||||||
|
Rte,
|
||||||
|
Rts,
|
||||||
|
Trapv,
|
||||||
|
Rtr,
|
||||||
|
Jsr(EffectiveAddress),
|
||||||
|
Jmp(EffectiveAddress),
|
||||||
|
Movem(MoveDirection, Size, EffectiveAddress, Vec<EffectiveAddress>),
|
||||||
|
Lea(u8, EffectiveAddress),
|
||||||
|
Chk(u8, EffectiveAddress),
|
||||||
|
Addq(Size, i8, EffectiveAddress),
|
||||||
|
Subq(Size, i8, EffectiveAddress),
|
||||||
|
Scc(Condition, EffectiveAddress),
|
||||||
|
Dbcc(Condition, u8, i16, u32),
|
||||||
|
Bra(Size, i16, u32),
|
||||||
|
Bsr(Size, i16, u32),
|
||||||
|
Bcc(Size, Condition, i16, u32),
|
||||||
|
Moveq {
|
||||||
|
dreg: u8,
|
||||||
|
imm: i8,
|
||||||
|
},
|
||||||
|
Divu(u8, EffectiveAddress),
|
||||||
|
Divs(u8, EffectiveAddress),
|
||||||
|
Sbcd {
|
||||||
|
src: EffectiveAddress,
|
||||||
|
dst: EffectiveAddress,
|
||||||
|
},
|
||||||
|
Or {
|
||||||
|
size: Size,
|
||||||
|
src: EffectiveAddress,
|
||||||
|
dst: EffectiveAddress,
|
||||||
|
},
|
||||||
|
Sub {
|
||||||
|
size: Size,
|
||||||
|
src: EffectiveAddress,
|
||||||
|
dst: EffectiveAddress,
|
||||||
|
},
|
||||||
|
Subx {
|
||||||
|
src: EffectiveAddress,
|
||||||
|
dst: EffectiveAddress,
|
||||||
|
size: Size,
|
||||||
|
},
|
||||||
|
Suba(u8, Size, EffectiveAddress),
|
||||||
|
Eor {
|
||||||
|
size: Size,
|
||||||
|
src: EffectiveAddress,
|
||||||
|
dst: EffectiveAddress,
|
||||||
|
},
|
||||||
|
Cmpm {
|
||||||
|
size: Size,
|
||||||
|
src: u8,
|
||||||
|
dst: u8,
|
||||||
|
},
|
||||||
|
Cmp(u8, Size, EffectiveAddress),
|
||||||
|
Cmpa(u8, Size, EffectiveAddress),
|
||||||
|
Mulu(u8, EffectiveAddress),
|
||||||
|
Muls(u8, EffectiveAddress),
|
||||||
|
Abcd {
|
||||||
|
src: EffectiveAddress,
|
||||||
|
dst: EffectiveAddress,
|
||||||
|
},
|
||||||
|
Exg {
|
||||||
|
src: EffectiveAddress,
|
||||||
|
dst: EffectiveAddress,
|
||||||
|
},
|
||||||
|
And {
|
||||||
|
size: Size,
|
||||||
|
src: EffectiveAddress,
|
||||||
|
dst: EffectiveAddress,
|
||||||
|
},
|
||||||
|
Add {
|
||||||
|
size: Size,
|
||||||
|
src: EffectiveAddress,
|
||||||
|
dst: EffectiveAddress,
|
||||||
|
},
|
||||||
|
Addx {
|
||||||
|
src: EffectiveAddress,
|
||||||
|
dst: EffectiveAddress,
|
||||||
|
size: Size,
|
||||||
|
},
|
||||||
|
Adda(u8, Size, EffectiveAddress),
|
||||||
|
Shift(ShiftType, Size, ShiftDirection, Rotation, EffectiveAddress),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Instruction {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::OriCcr(val) => write!(f, "ORI #{val:#x}, CCR"),
|
||||||
|
Self::OriSr(val) => write!(f, "ORI #{val:#x}, SR"),
|
||||||
|
Self::Ori(size, dst, val) => write!(f, "ORI.{size} #{val:#x}, {dst}"),
|
||||||
|
Self::AndiCcr(val) => write!(f, "ANDI #{val:#x}, CCR"),
|
||||||
|
Self::AndiSr(val) => write!(f, "ANDI #{val:#x}, SR"),
|
||||||
|
Self::Andi(size, dst, val) => write!(f, "ANDI.{size} #{val:#x}, {dst}"),
|
||||||
|
Self::Subi(size, dst, val) => write!(f, "SUBI.{size} #{val:#x}, {dst}"),
|
||||||
|
Self::Addi(size, dst, val) => write!(f, "ADDI.{size} #{val:#x}, {dst}"),
|
||||||
|
Self::EoriCcr(val) => write!(f, "EORI #{val:#x}, CCR"),
|
||||||
|
Self::EoriSr(val) => write!(f, "EORI #{val:#x}, SR"),
|
||||||
|
Self::Eori(size, dst, val) => write!(f, "EORI.{size} #{val:#x}, {dst}"),
|
||||||
|
Self::Cmpi(size, dst, val) => write!(f, "CMPI.{size} #{val:#x}, {dst}"),
|
||||||
|
Self::Bit {
|
||||||
|
typ,
|
||||||
|
idx,
|
||||||
|
dst,
|
||||||
|
size: _,
|
||||||
|
} => write!(f, "{typ} {idx}, {dst}"),
|
||||||
|
Self::Movep {
|
||||||
|
dir,
|
||||||
|
size,
|
||||||
|
dreg,
|
||||||
|
areg,
|
||||||
|
start_offset,
|
||||||
|
} => match dir {
|
||||||
|
MoveDirection::MemToReg => {
|
||||||
|
write!(f, "MOVEP.{size} ({start_offset:#x}, A{areg}), D{dreg}")
|
||||||
|
}
|
||||||
|
MoveDirection::RegToMem => {
|
||||||
|
write!(f, "MOVEP.{size} D{dreg}, ({start_offset:#x}, A{areg})")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Self::Move { src, dst, size } => write!(f, "MOVE.{size} {src}, {dst}"),
|
||||||
|
Self::MoveFromSr(dst) => write!(f, "MOVE SR, {dst}"),
|
||||||
|
Self::MoveToCcr(src) => write!(f, "MOVE {src}, CCR"),
|
||||||
|
Self::MoveToSr(src) => write!(f, "MOVE {src}, SR"),
|
||||||
|
Self::Negx(size, dst) => write!(f, "NEGX.{size} {dst}"),
|
||||||
|
Self::Clr(size, dst) => write!(f, "CLR.{size} {dst}"),
|
||||||
|
Self::Neg(size, dst) => write!(f, "NEG.{size} {dst}"),
|
||||||
|
Self::Not(size, dst) => write!(f, "NOT.{size} {dst}"),
|
||||||
|
Self::Ext(size, dreg) => write!(f, "EXT.{size} D{dreg}"),
|
||||||
|
Self::Nbcd(dst) => write!(f, "NBCD {dst}"),
|
||||||
|
Self::Swap(dreg) => write!(f, "SWAP D{dreg}"),
|
||||||
|
Self::Pea(ea) => write!(f, "PEA {ea}"),
|
||||||
|
Self::Tas(dst) => write!(f, "TAS {dst}"),
|
||||||
|
Self::Tst(size, src) => write!(f, "TST.{size} {src}"),
|
||||||
|
Self::Trap(vec) => write!(f, "TRAP #{vec:#x}"),
|
||||||
|
Self::Link { areg, displacement } => write!(f, "LINK A{areg}, #{displacement:#x}"),
|
||||||
|
Self::Unlk(areg) => write!(f, "UNLK A{areg}"),
|
||||||
|
Self::MoveUsp(dir, areg) => match dir {
|
||||||
|
MoveDirection::MemToReg => write!(f, "MOVE USP, A{areg}"),
|
||||||
|
MoveDirection::RegToMem => write!(f, "MOVE A{areg}, USP"),
|
||||||
|
},
|
||||||
|
Self::Reset => write!(f, "RESET"),
|
||||||
|
Self::Nop => write!(f, "NOP"),
|
||||||
|
Self::Stop(sr) => write!(f, "STOP #{sr:#x}"),
|
||||||
|
Self::Rte => write!(f, "RTE"),
|
||||||
|
Self::Rts => write!(f, "RTS"),
|
||||||
|
Self::Trapv => write!(f, "TRAPV"),
|
||||||
|
Self::Rtr => write!(f, "RTR"),
|
||||||
|
Self::Jsr(ea) => write!(f, "JSR {ea}"),
|
||||||
|
Self::Jmp(ea) => write!(f, "JMP {ea}"),
|
||||||
|
Self::Movem(dir, size, dst, regs) => {
|
||||||
|
let regs = regs
|
||||||
|
.iter()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("/");
|
||||||
|
match dir {
|
||||||
|
MoveDirection::MemToReg => write!(f, "MOVEM.{size} {regs}, A{dst}"),
|
||||||
|
MoveDirection::RegToMem => write!(f, "MOVEM.{size} {dst}, {regs}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Lea(areg, ea) => write!(f, "LEA {ea}, A{areg}"),
|
||||||
|
Self::Chk(src, bound) => write!(f, "CHK {bound}, D{src}"),
|
||||||
|
Self::Addq(size, val, dst) => write!(f, "ADDQ.{size} {val:#x}, {dst}"),
|
||||||
|
Self::Subq(size, val, dst) => write!(f, "SUBQ.{size} {val:#x}, {dst}"),
|
||||||
|
Self::Scc(cond, dst) => write!(f, "S{cond} {dst}"),
|
||||||
|
Self::Dbcc(cond, dreg, disp, new_pc) => {
|
||||||
|
write!(f, "DB{cond} {dreg}, {disp:#x} ({new_pc:#x})")
|
||||||
|
}
|
||||||
|
Self::Bra(size, disp, new_pc) => write!(f, "BRA.{size} {disp:#x} ({new_pc:#x})"),
|
||||||
|
Self::Bsr(size, disp, new_pc) => write!(f, "BSR.{size} {disp:#x} ({new_pc:#x})"),
|
||||||
|
Self::Bcc(size, cond, disp, new_pc) => {
|
||||||
|
write!(f, "B{cond}.{size} {disp:#x} ({new_pc:#x})")
|
||||||
|
}
|
||||||
|
Self::Moveq { dreg, imm } => write!(f, "MOVEQ #{imm:#x}, D{dreg}"),
|
||||||
|
Self::Divu(dst_dreg, src) => write!(f, "DIVU,W {src}, D{dst_dreg}"),
|
||||||
|
Self::Divs(dst_dreg, src) => write!(f, "DIVS.W {src}, D{dst_dreg}"),
|
||||||
|
Self::Sbcd { src, dst } => write!(f, "SBCD {src}, {dst}"),
|
||||||
|
Self::Or { size, src, dst } => write!(f, "OR.{size} {src}, {dst}"),
|
||||||
|
Self::Sub { size, src, dst } => write!(f, "SUB.{size} {src}, {dst}"),
|
||||||
|
Self::Subx { src, dst, size } => write!(f, "SUBX.{size} {src}, {dst}"),
|
||||||
|
Self::Suba(dst_areg, size, src) => write!(f, "SUBA.{size} {src}, A{dst_areg}"),
|
||||||
|
Self::Eor { size, src, dst } => write!(f, "EOR.{size} {src}, {dst}"),
|
||||||
|
Self::Cmpm { size, src, dst } => write!(f, "CMPM.{size} {src}, {dst}"),
|
||||||
|
Self::Cmp(dst_dreg, size, src) => write!(f, "CMP.{size} {src}, D{dst_dreg}"),
|
||||||
|
Self::Cmpa(dst_areg, size, src) => write!(f, "CMPA.{size} {src}, A{dst_areg}"),
|
||||||
|
Self::Mulu(dst_dreg, src) => write!(f, "MULU.W {src}, D{dst_dreg}"),
|
||||||
|
Self::Muls(dst_dreg, src) => write!(f, "MULS.W {src}, D{dst_dreg}"),
|
||||||
|
Self::Abcd { src, dst } => write!(f, "ABCD {src}, {dst}"),
|
||||||
|
Self::Exg { src, dst } => write!(f, "EXG {src}, {dst}"),
|
||||||
|
Self::And { size, src, dst } => write!(f, "AND.{size} {src}, {dst}"),
|
||||||
|
Self::Add { size, src, dst } => write!(f, "ADD.{size} {src}, {dst}"),
|
||||||
|
Self::Addx { src, dst, size } => write!(f, "ADDX.{size} {src}, {dst}"),
|
||||||
|
Self::Adda(dst_areg, size, src) => write!(f, "ADDA.{size} {src}, A{dst_areg}"),
|
||||||
|
Self::Shift(typ, size, dir, rot, dst) => {
|
||||||
|
write!(f, "{typ}{dir}.{size} ")?;
|
||||||
|
match dst {
|
||||||
|
EffectiveAddress::DataReg(_) => write!(f, "{rot}, {dst}"),
|
||||||
|
_ => write!(f, "{dst}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1259
src/m68k.rs
Normal file
1259
src/m68k.rs
Normal file
File diff suppressed because it is too large
Load Diff
429
src/main.rs
Normal file
429
src/main.rs
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
#![feature(bigint_helper_methods)]
|
||||||
|
|
||||||
|
mod backplane;
|
||||||
|
mod card;
|
||||||
|
mod disas;
|
||||||
|
mod instruction;
|
||||||
|
mod m68k;
|
||||||
|
mod ram;
|
||||||
|
mod rom;
|
||||||
|
mod storage;
|
||||||
|
use crate::{
|
||||||
|
backplane::Backplane,
|
||||||
|
m68k::{BusError, M68K},
|
||||||
|
};
|
||||||
|
use disas::DisassemblyError;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use parse_int::parse;
|
||||||
|
use reedline_repl_rs::{
|
||||||
|
clap::{Arg, ArgAction, Command},
|
||||||
|
Error as ReplError, Repl,
|
||||||
|
};
|
||||||
|
use serde_yaml::Mapping;
|
||||||
|
use std::{convert::TryFrom, fmt::Display, fs, num::ParseIntError, process};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Error {
|
||||||
|
Repl(ReplError),
|
||||||
|
InvalidCard(u8),
|
||||||
|
Bus(BusError),
|
||||||
|
InvalidPeekFormat,
|
||||||
|
InvalidPeekSize,
|
||||||
|
Disassembly(DisassemblyError<BusError>),
|
||||||
|
Misc(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DisassemblyError<BusError>> for Error {
|
||||||
|
fn from(v: DisassemblyError<BusError>) -> Self {
|
||||||
|
Self::Disassembly(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BusError> for Error {
|
||||||
|
fn from(v: BusError) -> Self {
|
||||||
|
Self::Bus(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ReplError> for Error {
|
||||||
|
fn from(v: ReplError) -> Self {
|
||||||
|
Self::Repl(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ParseIntError> for Error {
|
||||||
|
fn from(v: ParseIntError) -> Self {
|
||||||
|
Self::Repl(v.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Repl(e) => e.fmt(f),
|
||||||
|
Self::InvalidCard(n) => f.write_fmt(format_args!("Card {} does not exist", n)),
|
||||||
|
Self::Bus(e) => e.fmt(f),
|
||||||
|
Self::InvalidPeekFormat => f.write_str("Invalid peek format"),
|
||||||
|
Self::InvalidPeekSize => f.write_str("Invalid peek size"),
|
||||||
|
Self::Disassembly(e) => e.fmt(f),
|
||||||
|
Self::Misc(s) => f.write_str(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
enum PeekFormat {
|
||||||
|
Octal,
|
||||||
|
Hex,
|
||||||
|
Decimal,
|
||||||
|
UnsignedDecimal,
|
||||||
|
Binary,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PeekFormat {
|
||||||
|
/// Note: If the peek size was Byte or Word, the u8/u16 should be sign-extened to a u32 instaed
|
||||||
|
/// of directly cast. (u8/16 as i8/16 as i32 as u32) to allow Decimal to work.
|
||||||
|
pub fn format(self, num: u32, size: PeekSize) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Octal => format!("Oo{:0>width$o}", num, width = size.byte_count()),
|
||||||
|
Self::Hex => format!("0x{:0>width$x}", num, width = size.byte_count() * 2),
|
||||||
|
Self::Decimal => {
|
||||||
|
let num = match size {
|
||||||
|
PeekSize::Byte => num as u8 as i8 as i32,
|
||||||
|
PeekSize::Word => num as u16 as i16 as i32,
|
||||||
|
PeekSize::LongWord => num as i32,
|
||||||
|
};
|
||||||
|
format!("{}", num)
|
||||||
|
}
|
||||||
|
Self::UnsignedDecimal => format!("{}", num),
|
||||||
|
Self::Binary => format!("0b{:0>width$b}", num, width = size.byte_count() * 8),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<char> for PeekFormat {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
'o' => Ok(Self::Octal),
|
||||||
|
'x' => Ok(Self::Hex),
|
||||||
|
'd' => Ok(Self::Decimal),
|
||||||
|
'u' => Ok(Self::UnsignedDecimal),
|
||||||
|
'b' => Ok(Self::Binary),
|
||||||
|
_ => Err(Error::InvalidPeekFormat),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
enum PeekSize {
|
||||||
|
Byte,
|
||||||
|
Word,
|
||||||
|
LongWord,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PeekSize {
|
||||||
|
fn byte_count(self) -> usize {
|
||||||
|
match self {
|
||||||
|
PeekSize::Byte => 1,
|
||||||
|
PeekSize::Word => 2,
|
||||||
|
PeekSize::LongWord => 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chunk_size(self) -> usize {
|
||||||
|
match self {
|
||||||
|
PeekSize::Byte => 8,
|
||||||
|
PeekSize::Word => 8,
|
||||||
|
PeekSize::LongWord => 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<char> for PeekSize {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
'b' => Ok(Self::Byte),
|
||||||
|
'w' => Ok(Self::Word),
|
||||||
|
'l' => Ok(Self::LongWord),
|
||||||
|
_ => Err(Error::InvalidPeekSize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), ReplError> {
|
||||||
|
let config: Mapping = serde_yaml::from_str(
|
||||||
|
&fs::read_to_string("config.yaml").expect("Could not read config file"),
|
||||||
|
)
|
||||||
|
.expect("Could not parse config file");
|
||||||
|
let mut backplane = Backplane::new();
|
||||||
|
for card in config
|
||||||
|
.get("cards")
|
||||||
|
.expect("Could not get cards config info")
|
||||||
|
.as_sequence()
|
||||||
|
.expect("Cards config is not list")
|
||||||
|
{
|
||||||
|
let card = card.as_mapping().expect("Card config not mapping");
|
||||||
|
let typ = card
|
||||||
|
.get("type")
|
||||||
|
.expect("Card config has no type")
|
||||||
|
.as_str()
|
||||||
|
.expect("Card type is not string");
|
||||||
|
match backplane.add_card(typ, card) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => panic!("{}", e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Repl::<_, Error>::new(M68K::new(backplane))
|
||||||
|
.with_name("68KEmu")
|
||||||
|
.with_version("0.1.0")
|
||||||
|
.with_banner("68K Backplane Computer Emulator")
|
||||||
|
.with_description("68K Backplane Computer Emulator")
|
||||||
|
.with_command(
|
||||||
|
Command::new("card")
|
||||||
|
.trailing_var_arg(true)
|
||||||
|
.arg(
|
||||||
|
Arg::new("num")
|
||||||
|
.required(true)
|
||||||
|
.help("The card number to send the command to"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("args")
|
||||||
|
.required(true)
|
||||||
|
.multiple_values(true)
|
||||||
|
.takes_value(true),
|
||||||
|
)
|
||||||
|
.about("Send a command to a card"),
|
||||||
|
|args, cpu| {
|
||||||
|
let num = args.get_one::<String>("num").unwrap().parse::<u8>()?;
|
||||||
|
cpu.bus_mut()
|
||||||
|
.cards_mut()
|
||||||
|
.get_mut(num as usize)
|
||||||
|
.ok_or(Error::InvalidCard(num))?
|
||||||
|
.cmd(
|
||||||
|
&args
|
||||||
|
.get_many::<String>("args")
|
||||||
|
.unwrap()
|
||||||
|
.map(String::as_str)
|
||||||
|
.collect_vec(),
|
||||||
|
);
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_command(
|
||||||
|
Command::new("ls").about("List the cards in the system"),
|
||||||
|
|_, cpu| {
|
||||||
|
#[allow(unstable_name_collisions)]
|
||||||
|
Ok(Some(
|
||||||
|
cpu.bus_mut()
|
||||||
|
.cards()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, card)| format!("Card {i}: {card}"))
|
||||||
|
.intersperse('\n'.to_string())
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_command(
|
||||||
|
Command::new("regs").about("Show CPU registers"),
|
||||||
|
|_, cpu| Ok(Some(format!("{}", cpu))),
|
||||||
|
)
|
||||||
|
.with_command(
|
||||||
|
Command::new("step")
|
||||||
|
.arg(
|
||||||
|
Arg::new("count")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Count of instructions to step by. Defaults to 1"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("print_ins")
|
||||||
|
.long("print_ins")
|
||||||
|
.short('i')
|
||||||
|
.action(ArgAction::SetTrue)
|
||||||
|
.help("Print instructions")
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("print_regs")
|
||||||
|
.long("print_regs")
|
||||||
|
.short('r')
|
||||||
|
.action(ArgAction::SetTrue)
|
||||||
|
.help("Print ending registers")
|
||||||
|
)
|
||||||
|
.about("Step the CPU"),
|
||||||
|
|args, cpu| {
|
||||||
|
let count =
|
||||||
|
parse::<u32>(args.get_one::<String>("count").map_or("1", String::as_str))?;
|
||||||
|
let mut out = String::new();
|
||||||
|
for _ in 0..count {
|
||||||
|
if cpu.stopped {
|
||||||
|
out += &format!("CPU stopped at PC {:#x}\n", cpu.pc());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if args.get_flag("print_ins") {
|
||||||
|
out += &disas_fmt(cpu, cpu.pc()).0;
|
||||||
|
}
|
||||||
|
cpu.step();
|
||||||
|
}
|
||||||
|
if args.get_flag("print_regs") {
|
||||||
|
out += &format!("{}\n", cpu);
|
||||||
|
}
|
||||||
|
if out.is_empty() {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
out.pop(); // Remove trailing newline
|
||||||
|
Ok(Some(out))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_command(
|
||||||
|
Command::new("run")
|
||||||
|
.arg(
|
||||||
|
Arg::new("stop_addr")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Optional address to stop execution at. Works as a breakpoint only for this run")
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("print_ins")
|
||||||
|
.long("print_ins")
|
||||||
|
.short('p')
|
||||||
|
.action(ArgAction::SetTrue)
|
||||||
|
.help("Print all executed instructions")
|
||||||
|
)
|
||||||
|
.about("Run the CPU"),
|
||||||
|
|args, cpu| {
|
||||||
|
let mut out = String::new();
|
||||||
|
while !cpu.stopped {
|
||||||
|
let stop_addr = args
|
||||||
|
.get_one::<String>("stop_addr")
|
||||||
|
.map(|s| parse::<u32>(s))
|
||||||
|
.transpose()?;
|
||||||
|
if stop_addr.map(|a| cpu.pc() == a).unwrap_or(false) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if args.get_flag("print_ins") {
|
||||||
|
out += &disas_fmt(cpu, cpu.pc()).0;
|
||||||
|
}
|
||||||
|
cpu.step();
|
||||||
|
}
|
||||||
|
out += &format!("{}\n", cpu);
|
||||||
|
out += &disas_fmt(cpu, cpu.pc()).0;
|
||||||
|
out.pop(); // Remove trailing newline
|
||||||
|
Ok(Some(out))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_command(
|
||||||
|
Command::new("reset").about("Reset the cards and CPU, in that order"),
|
||||||
|
|_, cpu| {
|
||||||
|
for card in cpu.bus_mut().cards_mut() {
|
||||||
|
card.reset();
|
||||||
|
}
|
||||||
|
cpu.reset();
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_command(
|
||||||
|
Command::new("peek")
|
||||||
|
.arg(Arg::new("count").short('c').takes_value(true))
|
||||||
|
.arg(Arg::new("fmt").short('f').required(true).takes_value(true))
|
||||||
|
.arg(Arg::new("addr").required(true))
|
||||||
|
.about("Peek a memory address"),
|
||||||
|
|args, cpu| {
|
||||||
|
let fmt_str = args.get_one::<String>("fmt").unwrap();
|
||||||
|
if fmt_str.len() != 2 {
|
||||||
|
return Err(Error::Misc("Peek format length must be 2"));
|
||||||
|
}
|
||||||
|
let fmt = PeekFormat::try_from(fmt_str.chars().next().unwrap())?;
|
||||||
|
let size = PeekSize::try_from(fmt_str.chars().nth(1).unwrap())?;
|
||||||
|
let count =
|
||||||
|
parse::<u32>(args.get_one::<String>("count").map_or("1", String::as_str))?;
|
||||||
|
let addr = parse::<u32>(args.get_one::<String>("addr").unwrap())?;
|
||||||
|
|
||||||
|
let mut data = Vec::new();
|
||||||
|
let bus = cpu.bus_mut();
|
||||||
|
for i in 0..count {
|
||||||
|
match size {
|
||||||
|
PeekSize::Byte => data.push(bus.read_byte(addr + i)? as u32),
|
||||||
|
PeekSize::Word => data.push(bus.read_word(addr + (i * 2))? as u32),
|
||||||
|
PeekSize::LongWord => data.push(
|
||||||
|
(bus.read_word(addr + (i * 4))? as u32) << 16
|
||||||
|
| (bus.read_word(addr + (i * 4) + 2)? as u32),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[allow(unstable_name_collisions)]
|
||||||
|
Ok(Some(
|
||||||
|
data.chunks(size.chunk_size())
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, c)| {
|
||||||
|
format!(
|
||||||
|
"0x{:x}: ",
|
||||||
|
addr + (size.chunk_size() * size.byte_count() * i) as u32
|
||||||
|
) + &c
|
||||||
|
.iter()
|
||||||
|
.map(|d| fmt.format(*d, size))
|
||||||
|
.intersperse(" ".to_string())
|
||||||
|
.collect::<String>()
|
||||||
|
})
|
||||||
|
.intersperse("\n".to_string())
|
||||||
|
.collect::<String>(),
|
||||||
|
))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_command(
|
||||||
|
Command::new("disas")
|
||||||
|
.arg(
|
||||||
|
Arg::new("addr")
|
||||||
|
.help("Address to start disassembly at. Defaults to current PC"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("count")
|
||||||
|
.short('c')
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Count of instructions to disassemble. Defaults to 1"),
|
||||||
|
)
|
||||||
|
.about("Disassemble a region of memory"),
|
||||||
|
|args, cpu| {
|
||||||
|
let mut addr = args
|
||||||
|
.get_one::<String>("addr")
|
||||||
|
.map_or(Ok(cpu.pc()), |s| parse::<u32>(s))?;
|
||||||
|
let count =
|
||||||
|
parse::<u32>(args.get_one::<String>("count").map_or("1", String::as_str))?;
|
||||||
|
let mut out = String::new();
|
||||||
|
for _ in 0..count {
|
||||||
|
let (fmt, res) = disas_fmt(cpu, addr);
|
||||||
|
out += &fmt;
|
||||||
|
match res {
|
||||||
|
Ok(new_addr) => {
|
||||||
|
addr = new_addr;
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.pop(); // Remove trailing newline
|
||||||
|
Ok(Some(out))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_command(Command::new("quit")
|
||||||
|
.visible_alias("q")
|
||||||
|
.visible_alias("exit")
|
||||||
|
.about("Quit"),
|
||||||
|
|_, _| process::exit(0)
|
||||||
|
)
|
||||||
|
// Visible aliases don't actually work, so fake it with hidden subcommands
|
||||||
|
.with_command(Command::new("q").hide(true), |_, _| process::exit(0))
|
||||||
|
.with_command(Command::new("exit").hide(true), |_, _| process::exit(0))
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disas_fmt(cpu: &mut M68K, addr: u32) -> (String, Result<u32, DisassemblyError<BusError>>) {
|
||||||
|
match cpu.disassemble(addr) {
|
||||||
|
Ok((ins, new_addr)) => (format!("0x{:x}: {}\n", addr, ins), Ok(new_addr)),
|
||||||
|
Err(e) => (format!("0x{:x}: {}\n", addr, e), Err(e)),
|
||||||
|
}
|
||||||
|
}
|
99
src/ram.rs
Normal file
99
src/ram.rs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use human_repr::HumanCount;
|
||||||
|
use nullable_result::NullableResult;
|
||||||
|
use serde_yaml::Mapping;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
card::{u32_get_be_byte, Card},
|
||||||
|
m68k::BusError,
|
||||||
|
register,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Ram {
|
||||||
|
data: Vec<u8>,
|
||||||
|
start: u32,
|
||||||
|
enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ram {}
|
||||||
|
|
||||||
|
impl Card for Ram {
|
||||||
|
fn new(data: &Mapping) -> Self {
|
||||||
|
let size = data
|
||||||
|
.get("size")
|
||||||
|
.expect("No size value for RAM")
|
||||||
|
.as_u64()
|
||||||
|
.expect("Size value not positive integer");
|
||||||
|
Self {
|
||||||
|
data: vec![0; size as usize],
|
||||||
|
start: 0,
|
||||||
|
enabled: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn read_byte(&mut self, address: u32) -> NullableResult<u8, BusError> {
|
||||||
|
if !self.enabled {
|
||||||
|
return NullableResult::Null;
|
||||||
|
}
|
||||||
|
let address = address.checked_sub(self.start)?;
|
||||||
|
self.data.get(address as usize).copied().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_byte(&mut self, address: u32, data: u8) -> NullableResult<(), BusError> {
|
||||||
|
if !self.enabled {
|
||||||
|
return NullableResult::Null;
|
||||||
|
}
|
||||||
|
let address = address.checked_sub(self.start)?;
|
||||||
|
if address >= self.data.len() as u32 {
|
||||||
|
return NullableResult::Null;
|
||||||
|
}
|
||||||
|
self.data[address as usize] = data;
|
||||||
|
NullableResult::Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_byte_io(&mut self, address: u8, data: u8) -> NullableResult<(), BusError> {
|
||||||
|
match address {
|
||||||
|
0 => self.start = self.start & !0xFF00_0000 | (u32::from(data) << 24),
|
||||||
|
1 => self.start = self.start & !0x00FF_0000 | (u32::from(data) << 16),
|
||||||
|
2 => self.start = self.start & !0x0000_FF00 | (u32::from(data) << 8),
|
||||||
|
3 => {
|
||||||
|
self.start = self.start & !0x0000_00FF | u32::from(data & 0xFE);
|
||||||
|
self.enabled = data & 0x1 > 0;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
NullableResult::Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_byte_io(&mut self, address: u8) -> NullableResult<u8, BusError> {
|
||||||
|
match address {
|
||||||
|
0 => NullableResult::Ok((self.start >> 24) as u8),
|
||||||
|
1 => NullableResult::Ok((self.start >> 16) as u8),
|
||||||
|
2 => NullableResult::Ok((self.start >> 8) as u8),
|
||||||
|
3 => NullableResult::Ok((self.start as u8) | u8::from(self.enabled)),
|
||||||
|
(4..=7) => NullableResult::Ok(u32_get_be_byte(self.data.len() as u32, address - 4)),
|
||||||
|
0xFF => NullableResult::Ok(2),
|
||||||
|
_ => NullableResult::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Ram {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!(
|
||||||
|
"RAM card, size {}",
|
||||||
|
self.data.len().human_count_bytes()
|
||||||
|
))?;
|
||||||
|
if self.enabled {
|
||||||
|
f.write_fmt(format_args!(", enabled at base address {:#x}", self.start))?;
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register!(Ram, "ram");
|
165
src/rom.rs
Normal file
165
src/rom.rs
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
use std::{fmt::Display, fs::File, io::Read};
|
||||||
|
|
||||||
|
use human_repr::HumanCount;
|
||||||
|
use nullable_result::NullableResult;
|
||||||
|
use serde_yaml::Mapping;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
card::{u16_get_be_byte, u16_set_be_byte, Card},
|
||||||
|
m68k::BusError,
|
||||||
|
register,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Rom {
|
||||||
|
data: Vec<u8>,
|
||||||
|
enabled: bool,
|
||||||
|
ram: [u8; 32 * 1024],
|
||||||
|
file_name: Option<String>,
|
||||||
|
start: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rom {}
|
||||||
|
|
||||||
|
impl Card for Rom {
|
||||||
|
fn new(data: &Mapping) -> Self {
|
||||||
|
let file_name = data
|
||||||
|
.get("image")
|
||||||
|
.map(|name| name.as_str().expect("File name not string").to_string());
|
||||||
|
let mut data = Vec::new();
|
||||||
|
if let Some(file_name) = file_name.as_ref() {
|
||||||
|
File::open(file_name)
|
||||||
|
.unwrap_or_else(|e| panic!("Could not open ROM image file {} ({})", file_name, e))
|
||||||
|
.read_to_end(&mut data)
|
||||||
|
.unwrap_or_else(|e| panic!("Failed to read ROM image file {} ({})", file_name, e));
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
data,
|
||||||
|
enabled: true,
|
||||||
|
ram: [0; 32 * 1024],
|
||||||
|
file_name,
|
||||||
|
start: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_byte(&mut self, address: u32) -> NullableResult<u8, BusError> {
|
||||||
|
if !self.enabled | ((address >> 16) as u16 != self.start) {
|
||||||
|
return NullableResult::Null;
|
||||||
|
}
|
||||||
|
let address = address as u16;
|
||||||
|
if address < 0x4000 {
|
||||||
|
self.data.get(address as usize).copied().into()
|
||||||
|
} else {
|
||||||
|
self.ram.get((address - 0x4000) as usize).copied().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_byte(&mut self, address: u32, data: u8) -> NullableResult<(), BusError> {
|
||||||
|
if !self.enabled | ((address >> 16) as u16 != self.start) {
|
||||||
|
return NullableResult::Null;
|
||||||
|
}
|
||||||
|
let address = (address as u16).checked_sub(0x4000)?;
|
||||||
|
if address > self.data.len() as u16 {
|
||||||
|
return NullableResult::Null;
|
||||||
|
}
|
||||||
|
self.ram[address as usize] = data;
|
||||||
|
NullableResult::Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_byte_io(&mut self, address: u8) -> NullableResult<u8, BusError> {
|
||||||
|
match address {
|
||||||
|
(0..=0xEF) => NullableResult::Ok(self.ram[address as usize]),
|
||||||
|
(0xF0..=0xF1) => NullableResult::Ok(u16_get_be_byte(self.start, address - 0xF0)),
|
||||||
|
0xFE => NullableResult::Ok(self.enabled as u8),
|
||||||
|
0xFF => NullableResult::Ok(1),
|
||||||
|
_ => NullableResult::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_byte_io(&mut self, address: u8, data: u8) -> NullableResult<(), BusError> {
|
||||||
|
match address {
|
||||||
|
(0..=0xEF) => {
|
||||||
|
self.ram[address as usize] = data;
|
||||||
|
}
|
||||||
|
(0xF0..=0xF1) => {
|
||||||
|
self.start = u16_set_be_byte(self.start, address - 0xF0, data);
|
||||||
|
}
|
||||||
|
0xFE => {
|
||||||
|
self.enabled = data > 0;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
NullableResult::Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd(&mut self, cmd: &[&str]) {
|
||||||
|
if cmd[0] == "load" && cmd.len() >= 2 {
|
||||||
|
let mut file = match File::open(cmd[1]) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Could not open ROM image file {} ({})", cmd[1], e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.data.clear();
|
||||||
|
match file.read_to_end(&mut self.data) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to read ROM image file {} ({})", cmd[1], e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.file_name = Some(cmd[1].into());
|
||||||
|
println!("Read ROM image file {}", cmd[1]);
|
||||||
|
} else if cmd[0] == "reload" {
|
||||||
|
if let Some(file_name) = &self.file_name {
|
||||||
|
let mut file = match File::open(file_name) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Could not open ROM image file {} ({})", file_name, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.data.clear();
|
||||||
|
match file.read_to_end(&mut self.data) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to read ROM image file {} ({})", file_name, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
println!("Reloaded ROM image file {}", file_name);
|
||||||
|
} else {
|
||||||
|
println!("No ROM image file to reload");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Rom {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str("ROM card, ")?;
|
||||||
|
if let Some(name) = self.file_name.as_ref() {
|
||||||
|
f.write_fmt(format_args!(
|
||||||
|
"image {} ({})",
|
||||||
|
name,
|
||||||
|
self.data.len().human_count_bytes()
|
||||||
|
))?;
|
||||||
|
} else {
|
||||||
|
f.write_str("no image")?;
|
||||||
|
};
|
||||||
|
if self.enabled {
|
||||||
|
f.write_fmt(format_args!(
|
||||||
|
", enabled at base address {:#x}",
|
||||||
|
(self.start as u32) << 16
|
||||||
|
))?;
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register!(Rom, "rom");
|
127
src/storage.rs
Normal file
127
src/storage.rs
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
use std::{fmt::Display, fs::File, io::Read};
|
||||||
|
|
||||||
|
use human_repr::HumanCount;
|
||||||
|
use nullable_result::NullableResult;
|
||||||
|
use serde_yaml::Mapping;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
card::{u32_get_be_byte, u32_set_be_byte, Card},
|
||||||
|
m68k::BusError,
|
||||||
|
register,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SECTOR_SIZE: usize = 256;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Storage {
|
||||||
|
data: Vec<u8>,
|
||||||
|
sector: u32,
|
||||||
|
offset: usize,
|
||||||
|
file_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Card for Storage {
|
||||||
|
fn new(data: &Mapping) -> Self {
|
||||||
|
let file_name = data
|
||||||
|
.get("disk")
|
||||||
|
.map(|name| name.as_str().expect("File name not string").to_string());
|
||||||
|
let mut data = Vec::new();
|
||||||
|
if let Some(file_name) = file_name.as_ref() {
|
||||||
|
File::open(file_name)
|
||||||
|
.unwrap_or_else(|e| panic!("Could not open disk image file {} ({})", file_name, e))
|
||||||
|
.read_to_end(&mut data)
|
||||||
|
.unwrap_or_else(|e| panic!("Failed to read disk image file {} ({})", file_name, e));
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
data,
|
||||||
|
file_name,
|
||||||
|
sector: 0,
|
||||||
|
offset: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_byte_io(&mut self, address: u8) -> NullableResult<u8, BusError> {
|
||||||
|
match address {
|
||||||
|
0x0..=0x3 => NullableResult::Ok(u32_get_be_byte(self.sector, address)),
|
||||||
|
0x4 => {
|
||||||
|
let byte = self
|
||||||
|
.data
|
||||||
|
.get(self.sector as usize * SECTOR_SIZE + self.offset as usize)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(0);
|
||||||
|
self.offset += 1;
|
||||||
|
NullableResult::Ok(byte)
|
||||||
|
}
|
||||||
|
0xFF => NullableResult::Ok(4),
|
||||||
|
_ => NullableResult::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_byte_io(&mut self, address: u8, data: u8) -> NullableResult<(), BusError> {
|
||||||
|
if let 0x0..=0x3 = address {
|
||||||
|
self.sector = u32_set_be_byte(self.sector, address, data);
|
||||||
|
self.offset = 0;
|
||||||
|
}
|
||||||
|
NullableResult::Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd(&mut self, cmd: &[&str]) {
|
||||||
|
if cmd[0] == "load" && cmd.len() >= 2 {
|
||||||
|
let mut file = match File::open(cmd[1]) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Could not open disk image file {} ({})", cmd[1], e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.data.clear();
|
||||||
|
match file.read_to_end(&mut self.data) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to read disk image file {} ({})", cmd[1], e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.file_name = Some(cmd[1].into());
|
||||||
|
println!("Read disk image file {}", cmd[1]);
|
||||||
|
} else if cmd[0] == "reload" {
|
||||||
|
if let Some(file_name) = &self.file_name {
|
||||||
|
let mut file = match File::open(cmd[1]) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Could not open disk image file {} ({})", cmd[1], e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.data.clear();
|
||||||
|
match file.read_to_end(&mut self.data) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to read disk image file {} ({})", cmd[1], e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
println!("Reloaded disk image file {}", file_name);
|
||||||
|
} else {
|
||||||
|
println!("No disk image file to reload");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Storage {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str("Storage card, ")?;
|
||||||
|
if let Some(name) = self.file_name.as_ref() {
|
||||||
|
f.write_fmt(format_args!(
|
||||||
|
"disk image {} ({})",
|
||||||
|
name,
|
||||||
|
self.data.len().human_count_bytes(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
f.write_str("no disk image")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register!(Storage, "storage");
|
Loading…
Reference in New Issue
Block a user