Initial commit

This commit is contained in:
pjht 2022-10-09 12:14:49 -05:00
commit 722cdbc2f8
39 changed files with 73434 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

BIN
68k_ins.pdf Normal file

Binary file not shown.

940
Cargo.lock generated Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

2
build_rom.sh Normal file
View File

@ -0,0 +1,2 @@
vasmm68k_std rom.68k -o rom.bin -Fbin

8
config.yaml Normal file
View 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

Binary file not shown.

9
op_tbd Normal file
View File

@ -0,0 +1,9 @@
MOVEP
NBCD
SBCD
ABCD
ROXd

53
ram_map.c Executable file
View 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
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

View 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"]

View 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
}

View 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()
}

View 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()
}

View 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()
}

View 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()
}

View 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()
}

View 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()
}

View File

@ -0,0 +1,11 @@
{
"extends": [
"config:base"
],
"packageRules": [
{
"matchUpdateTypes": ["major","minor", "patch", "pin", "digest"],
"automerge": true
}
]
}

View 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),
}
}
}

View 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
}
}

View 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
View 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
}};
}

View 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();
}
}

View 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
View 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

BIN
rom.bin Normal file

Binary file not shown.

116
rom.lst Normal file
View 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
View File

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

133
src/backplane.rs Normal file
View 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
View 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
View 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(&reg_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
View 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

File diff suppressed because it is too large Load Diff

429
src/main.rs Normal file
View 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
View 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
View 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
View 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");