commit 373db681234c72bae105f9756907d4ff39e475b7 Author: zxq5 Date: Thu Jun 26 23:46:22 2025 +0100 initial commit diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..c5379ea --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,7 @@ +[build] +rustc-wrapper = "sccache" +# Enable to cut unused deps. +# rustflags = ["-D", "unused-crate-dependencies"] + +[future-incompat-report] +frequency = "always" diff --git a/.dsa.emulator.toml b/.dsa.emulator.toml new file mode 100644 index 0000000..a67d247 --- /dev/null +++ b/.dsa.emulator.toml @@ -0,0 +1,5 @@ +# The configuration file for the emulator. Here is an example file with all of the options you may want to set. + +[misc] +# Defaults to false, here for testing purposes. +use_discord_rpc = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a851572 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.env \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/damn_simple_architecture.iml b/.idea/damn_simple_architecture.iml new file mode 100644 index 0000000..7e831a2 --- /dev/null +++ b/.idea/damn_simple_architecture.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..3e8572f --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..6d501dc --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,3 @@ +max_width = 90 +comment_width = 90 +wrap_comments = true diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7ade51f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "rust-analyzer.check.command": "clippy", + "editor.formatOnSave": true, + "rust-analyzer.cargo.features": "all", + "files.eol": "\n", + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "files.trimTrailingWhitespace": true, + "gitea.owner": "LowLevelDevs", + "gitea.repo": "damn_simple_architecture", +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a84889a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4262 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ab_glyph" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + +[[package]] +name = "accesskit" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d3b8f9bae46a948369bc4a03e815d4ed6d616bd00de4051133a5019dc31c5a" + +[[package]] +name = "accesskit_atspi_common" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c5dd55e6e94949498698daf4d48fb5659e824d7abec0d394089656ceaf99d4f" +dependencies = [ + "accesskit", + "accesskit_consumer", + "atspi-common", + "serde", + "thiserror 1.0.69", + "zvariant", +] + +[[package]] +name = "accesskit_consumer" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47983a1084940ba9a39c077a8c63e55c619388be5476ac04c804cfbd1e63459" +dependencies = [ + "accesskit", + "hashbrown", + "immutable-chunkmap", +] + +[[package]] +name = "accesskit_macos" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7329821f3bd1101e03a7d2e03bd339e3ac0dc64c70b4c9f9ae1949e3ba8dece1" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "accesskit_unix" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcee751cc20d88678c33edaf9c07e8b693cd02819fe89053776f5313492273f5" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel", + "async-executor", + "async-task", + "atspi", + "futures-lite", + "futures-util", + "serde", + "zbus", +] + +[[package]] +name = "accesskit_windows" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24fcd5d23d70670992b823e735e859374d694a3d12bfd8dd32bd3bd8bedb5d81" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown", + "paste", + "static_assertions", + "windows", + "windows-core", +] + +[[package]] +name = "accesskit_winit" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6a48dad5530b6deb9fc7a52cc6c3bf72cdd9eb8157ac9d32d69f2427a5e879" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "raw-window-handle", + "winit", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.9.1", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[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 = "arboard" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1df21f715862ede32a0c525ce2ca4d52626bb0007f8c18b87a384503ac33e70" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.1", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", + "parking_lot", + "percent-encoding", + "windows-sys 0.59.0", + "x11rb", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "assembler" +version = "0.2.0" +dependencies = [ + "common", + "num_cpus", + "threadpool", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.0.7", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.0.7", + "tracing", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-signal" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.0.7", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atspi" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be534b16650e35237bb1ed189ba2aab86ce65e88cc84c66f4935ba38575cecbf" +dependencies = [ + "atspi-common", + "atspi-connection", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1909ed2dc01d0a17505d89311d192518507e8a056a48148e3598fef5e7bb6ba7" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus-lockstep", + "zbus-lockstep-macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-connection" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "430c5960624a4baaa511c9c0fcc2218e3b58f5dbcc47e6190cafee344b873333" +dependencies = [ + "atspi-common", + "atspi-proxies", + "futures-lite", + "zbus", +] + +[[package]] +name = "atspi-proxies" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e6c5de3e524cf967569722446bcd458d5032348554d9a17d7d72b041ab7496" +dependencies = [ + "atspi-common", + "serde", + "zbus", + "zvariant", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "serde", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" + +[[package]] +name = "bytemuck" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.9.1", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "colorful" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb474a9c3219a8254ead020421ecf1b90427f29b55f6aae9a2471fa62c126ef" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "common" +version = "0.2.0" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.60.2", +] + +[[package]] +name = "discord-presence" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91d7c2fc01ffdc327e2b66d65dd59b8bd3f31a17e88811ce0540412fa0b84c1" +dependencies = [ + "byteorder", + "bytes", + "cfg-if", + "crossbeam-channel", + "log", + "num-derive", + "num-traits", + "parking_lot", + "paste", + "quork", + "serde", + "serde_json", + "thiserror 2.0.12", + "uuid", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "dsa_editor" +version = "0.1.0" +dependencies = [ + "colorful", + "eframe", + "egui", + "serde", +] + +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + +[[package]] +name = "ecolor" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc4feb366740ded31a004a0e4452fbf84e80ef432ecf8314c485210229672fd1" +dependencies = [ + "bytemuck", + "emath", +] + +[[package]] +name = "eframe" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0dfe0859f3fb1bc6424c57d41e10e9093fe938f426b691e42272c2f336d915c" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "egui-wgpu", + "egui-winit", + "egui_glow", + "glow", + "glutin", + "glutin-winit", + "image", + "js-sys", + "log", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "parking_lot", + "percent-encoding", + "profiling", + "raw-window-handle", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "winapi", + "windows-sys 0.59.0", + "winit", +] + +[[package]] +name = "egui" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" +dependencies = [ + "accesskit", + "ahash", + "bitflags 2.9.1", + "emath", + "epaint", + "log", + "nohash-hasher", + "profiling", +] + +[[package]] +name = "egui-wgpu" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d319dfef570f699b6e9114e235e862a2ddcf75f0d1a061de9e1328d92146d820" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "epaint", + "log", + "profiling", + "thiserror 1.0.69", + "type-map", + "web-time", + "wgpu", + "winit", +] + +[[package]] +name = "egui-winit" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d9dfbb78fe4eb9c3a39ad528b90ee5915c252e77bbab9d4ebc576541ab67e13" +dependencies = [ + "accesskit_winit", + "ahash", + "arboard", + "bytemuck", + "egui", + "log", + "profiling", + "raw-window-handle", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_file" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e7289fecaa1af3f4944a7ac6e1c187d0700e32716c2a4c76d6bad7ffd255d72" +dependencies = [ + "dyn-clone", + "egui", +] + +[[package]] +name = "egui_glow" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "910906e3f042ea6d2378ec12a6fd07698e14ddae68aed2d819ffe944a73aab9e" +dependencies = [ + "ahash", + "bytemuck", + "egui", + "glow", + "log", + "memoffset", + "profiling", + "wasm-bindgen", + "web-sys", + "winit", +] + +[[package]] +name = "emath" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "emulator" +version = "0.1.0" +dependencies = [ + "assembler", + "common", + "dirs", + "discord-presence", + "dsa_editor", + "eframe", + "egui", + "egui_file", + "serde", + "toml", + "winit", +] + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "epaint" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fcc0f5a7c613afd2dee5e4b30c3e6acafb8ad6f0edb06068811f708a67c562" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "log", + "nohash-hasher", + "parking_lot", + "profiling", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7e7a64c02cf7a5b51e745a9e45f60660a286f151c238b9d397b3e923f5082f" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" +dependencies = [ + "bitflags 2.9.1", + "cfg_aliases", + "cgl", + "dispatch2", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "objc2 0.6.1", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "once_cell", + "raw-window-handle", + "wayland-sys", + "windows-sys 0.52.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" +dependencies = [ + "cfg_aliases", + "glutin", + "raw-window-handle", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" +dependencies = [ + "gl_generator", + "windows-sys 0.52.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.9.1", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.9.1", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", + "png", + "tiff", +] + +[[package]] +name = "immutable-chunkmap" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f97096f508d54f8f8ab8957862eee2ccd628847b6217af1a335e1c44dee578" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.2", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.9.1", + "libc", + "redox_syscall 0.5.13", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" +dependencies = [ + "bitflags 2.9.1", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "naga" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.9.1", + "cfg_aliases", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "rustc-hash 1.1.0", + "spirv", + "strum", + "termcolor", + "thiserror 2.0.12", + "unicode-xid", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.9.1", + "jni-sys", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.9.1", + "block2", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation 0.2.2", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.9.1", + "block2", + "dispatch", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation 0.2.2", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "orbclient" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" +dependencies = [ + "libredox", +] + +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.13", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.0.7", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + +[[package]] +name = "quork" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd9640e0addc098a3481fd53bdc23970e5dd0edf6b349403aa680fb576c8f83" +dependencies = [ + "cfg-if", + "nix", + "quork-proc", + "thiserror 2.0.12", + "windows-sys 0.59.0", +] + +[[package]] +name = "quork-proc" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "860d36740d9412e39fff90f57010e9870b15c2b48e5325295a6f5a824a480439" +dependencies = [ + "proc-macro-crate", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.12", +] + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.9.1", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" +dependencies = [ + "libc", + "smithay-client-toolkit", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix 1.0.7", + "windows-sys 0.59.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash 2.1.1", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wayland-backend" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" +dependencies = [ + "cc", + "downcast-rs", + "rustix 0.38.44", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" +dependencies = [ + "bitflags 2.9.1", + "rustix 0.38.44", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.9.1", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" +dependencies = [ + "rustix 0.38.44", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" +dependencies = [ + "bitflags 2.9.1", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" +dependencies = [ + "bitflags 2.9.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" +dependencies = [ + "bitflags 2.9.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" +dependencies = [ + "proc-macro2", + "quick-xml 0.37.5", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf4f3c0ba838e82b4e5ccc4157003fb8c324ee24c058470ffb82820becbde98" +dependencies = [ + "core-foundation 0.10.1", + "jni", + "log", + "ndk-context", + "objc2 0.6.1", + "objc2-foundation 0.3.1", + "url", + "web-sys", +] + +[[package]] +name = "weezl" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" + +[[package]] +name = "wgpu" +version = "24.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0b3436f0729f6cdf2e6e9201f3d39dc95813fad61d826c1ed07918b4539353" +dependencies = [ + "arrayvec", + "bitflags 2.9.1", + "cfg_aliases", + "document-features", + "js-sys", + "log", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "24.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f0aa306497a238d169b9dc70659105b4a096859a34894544ca81719242e1499" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.9.1", + "cfg_aliases", + "document-features", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.12", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "24.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f112f464674ca69f3533248508ee30cb84c67cf06c25ff6800685f5e0294e259" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bitflags 2.9.1", + "bytemuck", + "cfg_aliases", + "core-graphics-types", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-descriptor", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "ordered-float", + "parking_lot", + "profiling", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.12", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "windows", +] + +[[package]] +name = "wgpu-types" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" +dependencies = [ + "bitflags 2.9.1", + "js-sys", + "log", + "web-sys", +] + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[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" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winit" +version = "0.30.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.9.1", + "block2", + "bytemuck", + "calloop", + "cfg_aliases", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 0.38.44", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "xcursor" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "635887f4315a33cb714eb059bdbd7c1c92bfa71bc5b9d5115460502f788c2ab5" + +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.9.1", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus-lockstep" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca2c5dceb099bddaade154055c926bb8ae507a18756ba1d8963fd7b51d8ed1d" +dependencies = [ + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "zbus-lockstep", + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zbus_xml" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f374552b954f6abb4bd6ce979e6c9b38fb9d0cd7cc68a7d796e70c9f3a233" +dependencies = [ + "quick-xml 0.30.0", + "serde", + "static_assertions", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d5039da --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +cargo-features = ["codegen-backend"] + +[workspace] +members = ["emulator", "common", "assembler", "dsa_editor"] +resolver = "3" + +[workspace.package] +version = "0.2.0" +edition = "2024" +authors = ["zxq5", "nullndvoid"] + +[profile.dev] +codegen-backend = "cranelift" +panic = "abort" # Cranelift does not support stack unwinds. +lto = false +debug = true +incremental = false # sccache does not support caching incremental crates. diff --git a/README.md b/README.md new file mode 100644 index 0000000..98498f8 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +We failing DSA with this one \ No newline at end of file diff --git a/assembler/Cargo.toml b/assembler/Cargo.toml new file mode 100644 index 0000000..676d37d --- /dev/null +++ b/assembler/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "assembler" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[[bin]] +name = "assembler_runner" +path = "src/main.rs" + +[lib] +name = "assembler" +path = "src/lib.rs" + +[dependencies] +common = { path = "../common" } +num_cpus = "1.17.0" +threadpool = "1.8.1" diff --git a/assembler/brainf.bf b/assembler/brainf.bf new file mode 100644 index 0000000..4adf535 --- /dev/null +++ b/assembler/brainf.bf @@ -0,0 +1,29 @@ +++++++++++++++++++++++++++++++++++++++++++++ +>++++++++++++++++++++++++++++++++ +>++++++++++++++++ +> +>+ +<< +[ +>> +> +>++++++++++ +<< +[->+>-[>+>>]>[+[-<+>]>+>>]<<<<<<] +>[<+>-] +>[-] +>> +>++++++++++ +< +[->-[>+>>]>[+[-<+>]>+>>]<<<<<] +>[-] +>>[++++++++++++++++++++++++++++++++++++++++++++++++.[-]] +<[++++++++++++++++++++++++++++++++++++++++++++++++.[-]] +<<<++++++++++++++++++++++++++++++++++++++++++++++++.[-] +<<<<<<<.>. +>>[>>+<<-] +>[>+<<+>-] +>[<+>-] +<<<- +] +<<++... diff --git a/assembler/brainf.dsb b/assembler/brainf.dsb new file mode 100644 index 0000000..885a8c5 Binary files /dev/null and b/assembler/brainf.dsb differ diff --git a/assembler/src/assembler/assembler.rs b/assembler/src/assembler/assembler.rs new file mode 100644 index 0000000..5ba9e1c --- /dev/null +++ b/assembler/src/assembler/assembler.rs @@ -0,0 +1,264 @@ +use std::{ + collections::HashSet, + fs, + path::{self, Path, PathBuf}, + sync::{Arc, Mutex}, + thread::{self, JoinHandle}, +}; + +use crate::assembler::{AssembleError, Token, expand_pseudo_ops, lexer, quick_hash}; +use crate::assembler::{Node, Parser, resolve_dependencies}; +use crate::util::logging::Logger; + +// pub fn new_assemble(path: &Path) { +// let program = Program::new(); +// let program_ref = ProgramRef::new(program); + +// let task = Module::build(path.to_path_buf(), program_ref.clone()); +// program_ref.add_task(task); + +// // wait on all tasks to finish +// for task in program_ref.get_tasks() { +// let module = task.module.join().unwrap(); +// program_ref.add_module(module); +// } +// } + +pub struct Program { + pub main_path: PathBuf, + registry: HashSet, + modules: Vec, + tasks: Vec, + logger: Logger, +} + +impl Program { + #[must_use] + pub fn new() -> Self { + Self { + registry: HashSet::new(), + modules: Vec::new(), + tasks: Vec::new(), + main_path: PathBuf::new(), + logger: Logger::new(), + } + } + + pub fn add_task(&mut self, task: Task) { + self.tasks.push(task); + } +} + +impl Default for Program { + fn default() -> Self { + Self::new() + } +} + +pub struct ProgramRef { + program: Arc>, +} + +impl ProgramRef { + #[must_use] + pub fn new(program: Program) -> Self { + Self { + program: Arc::new(Mutex::new(program)), + } + } + + pub fn register(&self, path: &Path) { + self.program + .lock() + .expect("Failed to acquire program lock") + .registry + .insert(quick_hash(path)); + } + + #[must_use] + pub fn is_registered(&self, path: &Path) -> bool { + self.program + .lock() + .expect("Failed to acquire program lock") + .registry + .contains(&quick_hash(path)) + } + + // pub fn get_tasks(&self) -> Vec<&Task> { + // self.program.lock().unwrap().tasks.iter().collect() + // } + + pub fn add_task(&self, task: Task) { + self.program + .lock() + .expect("Failed to acquire program lock") + .add_task(task); + } + + pub fn add_module(&self, module: Module) { + self.program + .lock() + .expect("Failed to acquire program lock") + .modules + .push(module); + } + + pub fn log(&self, message: &str) { + self.program + .lock() + .expect("Failed to acquire program lock") + .logger + .log(message); + } +} + +impl Clone for ProgramRef { + fn clone(&self) -> Self { + Self { + program: self.program.clone(), + } + } +} + +pub struct Module { + pub path: PathBuf, + pub hash: u64, + pub nodes: Vec, + program: ProgramRef, +} + +impl Module { + #[must_use] + pub const fn new( + path: PathBuf, + hash: u64, + nodes: Vec, + program: ProgramRef, + ) -> Self { + Self { + path, + hash, + nodes, + program, + } + } + + pub fn build(path: PathBuf, program: ProgramRef) -> Result { + // Spawn a thread that creates the main function and executes the lexer and parser. + let handle = thread::spawn(move || { + let mut module = + Self::new(path.clone(), quick_hash(&path), Vec::new(), program.clone()); + + match module.lex() { + Ok(tokens) => { + module.parse(tokens); + module.expand(); + module.prepare_dependencies(); + module + } + Err(why) => { + eprintln!( + "Error building program at path `{}`: {why}", + path.display() + ); + + // TODO: Find a way to make this work without panicking. + unreachable!() + } + } + }); + + Ok(Task { module: handle }) + } + + fn lex(&self) -> Result, AssembleError> { + if let Ok(path) = self.path.canonicalize() { + self.program.log(&format!( + "{:20} {:20} [{}]", + "Building", + self.get_filename(), + path.display() + )); + } + + let src = fs::read_to_string(&self.path) + .map_err(|_| AssembleError::InvalidFile(self.path.clone()))?; + + let file_hash = quick_hash(&self.path); + + self.program + .log(&format!("{:20} {:20}", "Tokenising", self.get_filename())); + + lexer::lexer(src, file_hash) + } + + fn parse(&mut self, tokens: Vec) -> Result<(), AssembleError> { + self.program + .log(&format!("{:20} {:20}", "Parsing", self.get_filename())); + + let parsed = Parser::parse_nodes(tokens)?; + self.nodes = parsed; + + Ok(()) + } + + fn expand(&mut self) -> Result<(), AssembleError> { + self.program + .log(&format!("{:20} {:20}", "Expanding", self.get_filename())); + + let expanded = expand_pseudo_ops(self.nodes.clone(), self.hash)?; + self.nodes = expanded; + + Ok(()) + } + + fn prepare_dependencies(&self) -> Result<(), AssembleError> { + let nodes = resolve_dependencies( + self.nodes.clone(), + self.path.parent().expect("File should have a parent path!"), + )?; + + let dependencies = Parser::get_dependencies(&nodes, &self.path)?; + + for dep in dependencies { + if self.program.is_registered(&dep) { + // we have already built this module! + continue; + } + + self.program.register(&dep); + + // create new module + // add the task to the program + + match Self::build(dep, self.program.clone()) { + Ok(task) => self.program.add_task(task), + Err(why) => { + eprintln!("Error building program: {why}"); + } + } + } + + Ok(()) + } + + /// Gets the filename from a [`PathBuf`]. + fn get_filename(&self) -> &str { + self.path + .file_name() + .and_then(|f| f.to_str()) + .unwrap_or_default() + } + + /// Gets the parent filepath from a [`PathBuf`]. + fn get_parent(&self) -> &str { + self.path + .parent() + .and_then(|f| f.to_str()) + .unwrap_or_default() + } +} + +pub struct Task { + module: JoinHandle, +} diff --git a/assembler/src/assembler/codegen.rs b/assembler/src/assembler/codegen.rs new file mode 100644 index 0000000..3e34281 --- /dev/null +++ b/assembler/src/assembler/codegen.rs @@ -0,0 +1,348 @@ +use common::{args, prelude::*}; + +use crate::assembler::model::{Node, Opcode}; +use crate::{assembler::AssembleError, expect_token}; + +fn log(message: &str) { + println!("\x1b[32mINFO:\x1b[0m {message}"); +} + +pub fn codegen(nodes: Vec) -> Result, AssembleError> { + let mut instructions = vec![]; + + for node in nodes { + instructions.push(build_instruction(&node)?); + } + + println!("------------------------"); + log("Compilation Success ✅"); + + Ok(instructions) +} + +fn build_instruction(node: &Node) -> Result { + let opcode = node.opcode(); + let args = node.args(); + + match opcode { + Opcode::Nop => Ok(Instruction::Nop), + Opcode::Mov | Opcode::Movs => build_mov_instruction(opcode, &args), + Opcode::Ldb + | Opcode::Ldw + | Opcode::Ldh + | Opcode::Ldbs + | Opcode::Ldhs + | Opcode::Stb + | Opcode::Stw + | Opcode::Sth => build_memory_instruction(opcode, &args), + Opcode::Lli | Opcode::Lui => build_load_immediate_instruction(opcode, &args), + Opcode::Jmp + | Opcode::Jeq + | Opcode::Jne + | Opcode::Jgt + | Opcode::Jge + | Opcode::Jlt + | Opcode::Jle => build_jump_instruction(opcode, &args), + Opcode::Cmp => build_compare_instruction(&args), + Opcode::Inc | Opcode::Dec => build_inc_dec_instruction(opcode, &args), + Opcode::Shl | Opcode::Shr => build_shift_instruction(opcode, &args), + Opcode::Add + | Opcode::Sub + | Opcode::And + | Opcode::Or + | Opcode::Xor + | Opcode::Nand + | Opcode::Nor + | Opcode::Xnor => build_arithmetic_instruction(opcode, &args), + Opcode::AddI | Opcode::SubI => { + build_arithmetic_immediate_instruction(opcode, &args) + } + Opcode::Not => build_not_instruction(&args), + Opcode::Int => build_interrupt_instruction(&args), + Opcode::Irt => Ok(Instruction::IntReturn), + Opcode::Hlt => Ok(Instruction::Halt), + Opcode::Data => build_data_instruction(&args), + Opcode::Segment => build_segment_instruction(&args), + // These pseudo-instructions should have been expanded! + Opcode::Db + | Opcode::Dh + | Opcode::Dw + | Opcode::Resb + | Opcode::Resh + | Opcode::Resw + | Opcode::Push + | Opcode::Pop + | Opcode::Lwi + | Opcode::Include + | Opcode::Call + | Opcode::Return + | Opcode::Pusha + | Opcode::Popa => Err(AssembleError::InvalidArg), + } +} + +fn build_mov_instruction( + opcode: Opcode, + args: &[crate::assembler::model::Token], +) -> Result { + let Some(src_token) = args.first() else { + return Err(AssembleError::MissingArgument(0)); + }; + let Some(dest_token) = args.get(1) else { + return Err(AssembleError::MissingArgument(1)); + }; + + let src = expect_token!(src_token, Register)?; + let dest = expect_token!(dest_token, Register)?; + + match opcode { + Opcode::Mov => Ok(Instruction::Mov(args!(R, sr1: src, dr: dest))), + Opcode::Movs => Ok(Instruction::MovSigned(args!(R, sr1: src, dr: dest))), + _ => unreachable!(), + } +} + +fn build_memory_instruction( + opcode: Opcode, + args: &[crate::assembler::model::Token], +) -> Result { + let Some(base_token) = args.first() else { + return Err(AssembleError::MissingArgument(0)); + }; + let Some(dest_token) = args.get(1) else { + return Err(AssembleError::MissingArgument(1)); + }; + let Some(offset_token) = args.get(2) else { + return Err(AssembleError::MissingArgument(2)); + }; + + let base = expect_token!(base_token, Register)?; + let dest = expect_token!(dest_token, Register)?; + let offset = expect_token!(offset_token, Immediate)?; + let instruction_args = args!(I, immediate: offset as u16, r1: base, r2: dest); + + match opcode { + Opcode::Ldb => Ok(Instruction::LoadByte(instruction_args)), + Opcode::Ldw => Ok(Instruction::LoadWord(instruction_args)), + Opcode::Ldh => Ok(Instruction::LoadHalfword(instruction_args)), + Opcode::Ldbs => Ok(Instruction::LoadByteSigned(instruction_args)), + Opcode::Ldhs => Ok(Instruction::LoadHalfwordSigned(instruction_args)), + Opcode::Stb => Ok(Instruction::StoreByte(instruction_args)), + Opcode::Stw => Ok(Instruction::StoreWord(instruction_args)), + Opcode::Sth => Ok(Instruction::StoreHalfword(instruction_args)), + _ => unreachable!(), + } +} + +fn build_load_immediate_instruction( + opcode: Opcode, + args: &[crate::assembler::model::Token], +) -> Result { + let Some(value_token) = args.first() else { + return Err(AssembleError::MissingArgument(0)); + }; + let Some(dest_token) = args.get(1) else { + return Err(AssembleError::MissingArgument(1)); + }; + + let value = expect_token!(value_token, Immediate)?; + let dest = expect_token!(dest_token, Register)?; + + match opcode { + Opcode::Lli => { + let instruction_args = args!(I, immediate: value as u16, r1: dest); + Ok(Instruction::LoadLowerImmediate(instruction_args)) + } + Opcode::Lui => { + let upper_value = value >> 16; + let instruction_args = args!(I, immediate: upper_value as u16, r1: dest); + Ok(Instruction::LoadUpperImmediate(instruction_args)) + } + _ => unreachable!(), + } +} + +fn build_jump_instruction( + opcode: Opcode, + args: &[crate::assembler::model::Token], +) -> Result { + let Some(address_token) = args.first() else { + return Err(AssembleError::MissingArgument(0)); + }; + let Some(offset_token) = args.get(1) else { + return Err(AssembleError::MissingArgument(1)); + }; + + let address = expect_token!(address_token, Immediate)?; + let offset = expect_token!(offset_token, Register)?; + let instruction_args = args!(I, immediate: address as u16, r1: offset); + + match opcode { + Opcode::Jmp => Ok(Instruction::Jump(instruction_args)), + Opcode::Jeq => Ok(Instruction::JumpEq(instruction_args)), + Opcode::Jne => Ok(Instruction::JumpNeq(instruction_args)), + Opcode::Jgt => Ok(Instruction::JumpGt(instruction_args)), + Opcode::Jge => Ok(Instruction::JumpGe(instruction_args)), + Opcode::Jlt => Ok(Instruction::JumpLt(instruction_args)), + Opcode::Jle => Ok(Instruction::JumpLe(instruction_args)), + _ => unreachable!(), + } +} + +fn build_compare_instruction( + args: &[crate::assembler::model::Token], +) -> Result { + let Some(left_token) = args.first() else { + return Err(AssembleError::MissingArgument(0)); + }; + let Some(right_token) = args.get(1) else { + return Err(AssembleError::MissingArgument(1)); + }; + + let left = expect_token!(left_token, Register)?; + let right = expect_token!(right_token, Register)?; + Ok(Instruction::Compare(args!(R, sr1: left, sr2: right))) +} + +fn build_inc_dec_instruction( + opcode: Opcode, + args: &[crate::assembler::model::Token], +) -> Result { + let Some(reg_token) = args.first() else { + return Err(AssembleError::MissingArgument(0)); + }; + + let reg = expect_token!(reg_token, Register)?; + match opcode { + Opcode::Inc => Ok(Instruction::Increment(args!(R, sr1: reg))), + Opcode::Dec => Ok(Instruction::Decrement(args!(R, sr1: reg))), + _ => unreachable!(), + } +} + +fn build_shift_instruction( + opcode: Opcode, + args: &[crate::assembler::model::Token], +) -> Result { + let Some(reg_token) = args.first() else { + return Err(AssembleError::MissingArgument(0)); + }; + let Some(amount_token) = args.get(1) else { + return Err(AssembleError::MissingArgument(1)); + }; + + let reg = expect_token!(reg_token, Register)?; + let amount = expect_token!(amount_token, Immediate)? as u8; + + match opcode { + Opcode::Shl => Ok(Instruction::ShiftLeft(args!(R, sr1: reg, shamt: amount))), + Opcode::Shr => Ok(Instruction::ShiftRight(args!(R, sr1: reg, shamt: amount))), + _ => unreachable!(), + } +} + +fn build_arithmetic_instruction( + opcode: Opcode, + args: &[crate::assembler::model::Token], +) -> Result { + let Some(left_token) = args.first() else { + return Err(AssembleError::MissingArgument(0)); + }; + let Some(right_token) = args.get(1) else { + return Err(AssembleError::MissingArgument(1)); + }; + let Some(dest_token) = args.get(2) else { + return Err(AssembleError::MissingArgument(2)); + }; + + let left = expect_token!(left_token, Register)?; + let right = expect_token!(right_token, Register)?; + let dest = expect_token!(dest_token, Register)?; + let instruction_args = args!(R, sr1: left, sr2: right, dr: dest); + + match opcode { + Opcode::Add => Ok(Instruction::Add(instruction_args)), + Opcode::Sub => Ok(Instruction::Sub(instruction_args)), + Opcode::And => Ok(Instruction::And(instruction_args)), + Opcode::Or => Ok(Instruction::Or(instruction_args)), + Opcode::Xor => Ok(Instruction::Xor(instruction_args)), + Opcode::Nand => Ok(Instruction::Nand(instruction_args)), + Opcode::Nor => Ok(Instruction::Nor(instruction_args)), + Opcode::Xnor => Ok(Instruction::Xnor(instruction_args)), + _ => unreachable!(), + } +} + +fn build_arithmetic_immediate_instruction( + opcode: Opcode, + args: &[crate::assembler::model::Token], +) -> Result { + let Some(reg_token) = args.first() else { + return Err(AssembleError::MissingArgument(0)); + }; + let Some(immediate_token) = args.get(1) else { + return Err(AssembleError::MissingArgument(1)); + }; + let Some(dest_token) = args.get(2) else { + return Err(AssembleError::MissingArgument(2)); + }; + + let reg = expect_token!(reg_token, Register)?; + let immediate = expect_token!(immediate_token, Immediate)? as u16; + let dest = expect_token!(dest_token, Register)?; + let instruction_args = args!(I, immediate: immediate, r1: reg, r2: dest); + + match opcode { + Opcode::AddI => Ok(Instruction::AddImmediate(instruction_args)), + Opcode::SubI => Ok(Instruction::SubImmediate(instruction_args)), + _ => unreachable!(), + } +} + +fn build_not_instruction( + args: &[crate::assembler::model::Token], +) -> Result { + let Some(reg_token) = args.first() else { + return Err(AssembleError::MissingArgument(0)); + }; + let Some(dest_token) = args.get(1) else { + return Err(AssembleError::MissingArgument(1)); + }; + + let reg = expect_token!(reg_token, Register)?; + let dest = expect_token!(dest_token, Register)?; + Ok(Instruction::Not(args!(R, sr1: reg, dr: dest))) +} + +fn build_interrupt_instruction( + args: &[crate::assembler::model::Token], +) -> Result { + let Some(code_token) = args.first() else { + return Err(AssembleError::MissingArgument(0)); + }; + + let code = expect_token!(code_token, Immediate)? as u8; + Ok(Instruction::Interrupt(Interrupt::Software(code))) +} + +fn build_data_instruction( + args: &[crate::assembler::model::Token], +) -> Result { + let Some(immediate_token) = args.first() else { + return Err(AssembleError::MissingArgument(0)); + }; + + let immediate = expect_token!(immediate_token, Immediate)?; + Ok(Instruction::Data(immediate)) +} + +fn build_segment_instruction( + args: &[crate::assembler::model::Token], +) -> Result { + let Some(immediate_token) = args.first() else { + return Err(AssembleError::MissingArgument(0)); + }; + + let immediate = expect_token!(immediate_token, Immediate)?; + Ok(Instruction::Segment(immediate)) +} diff --git a/assembler/src/assembler/expand.rs b/assembler/src/assembler/expand.rs new file mode 100644 index 0000000..ed25bd3 --- /dev/null +++ b/assembler/src/assembler/expand.rs @@ -0,0 +1,368 @@ +use common::prelude::Register; + +use crate::assembler::model::{Node, Opcode, Token}; +use crate::{assembler::AssembleError, expect_token, expect_type, node}; + +pub fn expand_pseudo_ops( + mut nodes: Vec, + module: u64, +) -> Result, AssembleError> { + let mut result = Vec::::with_capacity(nodes.len()); + + for node in &mut nodes { + if try_expand(node.clone(), &mut result, module).is_err() { + result.push(node.clone()); + } + } + + Ok(result) +} + +fn try_expand( + node: Node, + result: &mut Vec, + _module: u64, +) -> Result<(), AssembleError> { + match node.opcode() { + Opcode::Push => expand_push(&node, result)?, + Opcode::Pop => expand_pop(&node, result)?, + Opcode::Pusha => expand_pusha(&node, result)?, + Opcode::Popa => expand_popa(&node, result)?, + Opcode::Call => expand_call(&node, result)?, + Opcode::Return => expand_return(&node, result), + Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw => { + expand_ldx(&node, result)?; + } + Opcode::Stb | Opcode::Sth | Opcode::Stw => expand_stx(&node, result)?, + + Opcode::Lwi => expand_lwi(&node, result)?, + Opcode::Resb | Opcode::Resh | Opcode::Resw => expand_resx(&node, result)?, + Opcode::Db | Opcode::Dh | Opcode::Dw => expand_dx(&node, result)?, + _ => result.push(node), + } + Ok(()) +} + +fn expand_push(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + let label = current.label(); + let Ok(arg0) = current.arg(0) else { + return Err(AssembleError::Generic); + }; + + let reg = expect_type!(arg0, Register)?; + let spr = Token::Register(Register::Spr); + + nodes.extend(vec![ + node!(label, Opcode::SubI, spr, 4, spr), + node!(None, Opcode::Stw, reg, spr, 0), + ]); + + Ok(()) +} + +fn expand_pusha(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + let label = current.label(); + let Ok(arg0) = current.arg(0) else { + return Err(AssembleError::Generic); + }; + + let count = expect_token!(arg0, Immediate)?; + let spr = Token::Register(Register::Spr); + let registers: Vec = Register::general(); + + nodes.push(node!( + label, + Opcode::SubI, + spr, + Token::Immediate(count * 4), + spr + )); + + nodes.extend((0..count).rev().map(|i| { + node!( + None, + Opcode::Stw, + Token::Register(registers[i as usize]), + spr, + Token::Immediate(i * 4) + ) + })); + + Ok(()) +} + +fn expand_popa(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + let label = current.label(); + + let Ok(arg0) = current.arg(0) else { + return Err(AssembleError::Generic); + }; + + let count = expect_token!(arg0, Immediate)?; + let spr = Token::Register(Register::Spr); + let registers: Vec = Register::general(); + + nodes.extend((0..count).rev().map(|i| { + node!( + { if i == 0 { label.clone() } else { None } }, + Opcode::Ldw, + spr, + Token::Register(registers[i as usize]), + Token::Immediate(i * 4) + ) + })); + + nodes.push(node!( + None, + Opcode::AddI, + spr, + Token::Immediate(count * 4), + spr + )); + + Ok(()) +} + +fn expand_call(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + let label = current.label(); + + let Ok(arg0) = current.arg(0) else { + return Err(AssembleError::Generic); + }; + + let addr = expect_type!(arg0, Symbol)?; + let spr = Token::Register(Register::Spr); + let pcx = Token::Register(Register::Pcx); + let zero = Token::Register(Register::Zero); + + nodes.extend(vec![ + node!(label, Opcode::SubI, spr, 4, spr), + node!(None, Opcode::Stw, pcx, spr, 0), + node!(None, Opcode::Jmp, addr, zero), + ]); + + Ok(()) +} + +fn expand_return(current: &Node, nodes: &mut Vec) { + let label = current.label(); + let spr = Token::Register(Register::Spr); + let ret = Token::Register(Register::Ret); + + nodes.extend(vec![ + node!(label, Opcode::Ldw, spr, ret, 0), + node!(None, Opcode::AddI, spr, 4, spr), + node!(None, Opcode::Jmp, 4, ret), + ]); +} + +fn expand_pop(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + let label = current.label(); + + let Ok(arg0) = current.arg(0) else { + return Err(AssembleError::Generic); + }; + + let reg = expect_type!(arg0, Register)?; + let spr = Token::Register(Register::Spr); + + nodes.extend(vec![ + node!(label, Opcode::Ldw, spr, reg, 0), + node!(None, Opcode::AddI, spr, 4, spr), + ]); + + Ok(()) +} + +fn expand_ldx(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + let opcode = current.opcode(); + let args: Vec = current.args().into_iter().take(3).collect(); + + let Some(name) = args.first() else { + return Err(AssembleError::MissingArgument(0)); + }; + + let Some(reg) = args.get(1) else { + return Err(AssembleError::MissingArgument(1)); + }; + + let Some(offset) = args.get(2) else { + return Err(AssembleError::MissingArgument(2)); + }; + + let name = expect_type!(name, Symbol)?; + let reg = expect_type!(reg, Register)?; + let offset = expect_type!(offset, Immediate)?; + + nodes.extend(vec![ + node!(current.label(), Opcode::Lli, name, reg), + node!(None, Opcode::Lui, name, reg), + node!(None, opcode, reg, reg, offset), + ]); + + Ok(()) +} + +fn expand_stx(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + let opcode = current.opcode(); + + let args: Vec = current.args().into_iter().take(3).collect(); + + let Some(base) = args.first() else { + return Err(AssembleError::MissingArgument(0)); + }; + + let Some(dest) = args.get(1) else { + return Err(AssembleError::MissingArgument(1)); + }; + + let Some(offset) = args.get(2) else { + return Err(AssembleError::MissingArgument(2)); + }; + + let base = expect_type!(base, Register)?; + let dest = expect_type!(dest, Symbol)?; + let offset = expect_type!(offset, Immediate)?; + let temp = Token::Register(Register::Acc); + + nodes.extend(vec![ + node!(current.label(), Opcode::Lli, dest, temp), + node!(None, Opcode::Lui, dest, temp), + node!(None, opcode, base, temp, offset), + ]); + + Ok(()) +} + +fn expand_lwi(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + let Ok(val) = current.arg(0) else { + return Err(AssembleError::MissingArgument(0)); + }; + + let Ok(reg) = current.arg(1) else { + return Err(AssembleError::MissingArgument(1)); + }; + + let val = expect_type!(val, Symbol, Immediate)?; + let reg = expect_type!(reg, Register)?; + + nodes.extend(vec![ + node!(current.label(), Opcode::Lli, val, reg), + node!(None, Opcode::Lui, val, reg), + ]); + + Ok(()) +} + +fn expand_resx(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + let Ok(region_label) = current.arg(0) else { + return Err(AssembleError::MissingArgument(0)); + }; + + let Ok(size) = current.arg(1) else { + return Err(AssembleError::MissingArgument(1)); + }; + + let region_label = expect_token!(region_label, Symbol)?; + let size = expect_token!(size, Immediate)?; + + let units_per = match current.opcode() { + Opcode::Resb => 4, + Opcode::Resh => 2, + Opcode::Resw => 1, + _ => unreachable!(), + }; + + let mut buffer = vec![]; + // push the inital node with the label + for _ in 0..size.div_ceil(units_per) { + // push the rest of the nodes + buffer.push(node!(None, Opcode::Data, 0)); + } + buffer[0].symbol = Some(region_label); + nodes.extend(buffer); + + Ok(()) +} + +fn expand_dx(current: &Node, nodes: &mut Vec) -> Result<(), AssembleError> { + let Ok(region_label) = current.arg(0) else { + return Err(AssembleError::MissingArgument(0)); + }; + + let region_label = expect_token!(region_label, Symbol)?; + let size = match current.opcode() { + Opcode::Db => 4, + Opcode::Dh => 2, + Opcode::Dw => 1, + _ => unreachable!(), + }; + + let mut buffer = vec![]; + + let mut args = current.args(); + let _label = args.remove(0); + + for word in process_dx_data(args, size)? { + buffer.push(node!(None, Opcode::Data, Token::Immediate(word))); + } + buffer[0].symbol = Some(region_label); + + nodes.extend(buffer); + Ok(()) +} + +fn process_dx_data(args: Vec, size: usize) -> Result, AssembleError> { + assert!(matches!(size, 1 | 2 | 4)); + + let mut buffer = Vec::::new(); + + // Process each token + for token in args { + match token { + Token::StringLit(mut s) => { + s.push('\0'); + // Split string into chars and write as bytes + for ch in s.chars() { + // Convert char to bytes (UTF-8 encoding) + let mut char_buf = [0u8; 4]; + let char_bytes = ch.encode_utf8(&mut char_buf); + buffer.extend_from_slice(char_bytes.as_bytes()); + } + } + Token::Immediate(value) => { + // Split u32 into bytes (little-endian) + buffer.extend_from_slice(&value.to_be_bytes()); + } + _ => { + return Err(AssembleError::Generic); + } + } + + // Pad buffer to alignment boundary with zeros + let remainder = buffer.len() % size; + if remainder != 0 { + let padding = size - remainder; + buffer.resize(buffer.len() + padding, 0); + } + } + + // Convert byte buffer to u32 chunks + // Pad final buffer to u32 boundary if needed + let remainder = buffer.len() % 4; + if remainder != 0 { + let padding = 4 - remainder; + buffer.resize(buffer.len() + padding, 0); + } + + // Convert bytes to u32s efficiently using chunks_exact + let result = buffer + .chunks_exact(4) + .map(|chunk| { + // Convert 4 bytes to u32 (little-endian) + u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]) + }) + .collect(); + + Ok(result) +} diff --git a/assembler/src/assembler/lexer.rs b/assembler/src/assembler/lexer.rs new file mode 100644 index 0000000..442d9b3 --- /dev/null +++ b/assembler/src/assembler/lexer.rs @@ -0,0 +1,173 @@ +use std::str::FromStr; + +use crate::assembler::AssembleError; +use crate::assembler::model::{Module, Opcode, Symbol, Token}; +use common::prelude::Register; + +pub fn lexer(mut program: String, module: u64) -> Result, AssembleError> { + let mut tokens = Vec::new(); + + let lines = program.lines(); + let mut literal = String::new(); + + for line in lines { + for (i, token) in line.split_whitespace().enumerate() { + if token.starts_with("//") { + break; + } + + if let Some(stripped) = token.strip_prefix('"') { + literal.push_str(stripped); + } + + if !literal.is_empty() { + if !token.starts_with('"') { + if i > 0 { + literal.push(' '); + } + literal.push_str(token); + } + + if token.ends_with('"') { + literal.pop(); // remove the closing quote + + tokens.push(Token::StringLit(literal)); + literal = String::new(); + } + + continue; + } + + let token = token.trim_end_matches(','); + if token.is_empty() { + continue; + } + + if let Some(token) = parse_register(token)? { + tokens.push(token); + } else if let Some(token) = parse_opcode(token)? { + tokens.push(token); + } else if let Some(token) = parse_hex(token)? { + tokens.push(token); + } else if let Some(token) = parse_octal(token)? { + tokens.push(token); + } else if let Some(token) = parse_binary(token)? { + tokens.push(token); + } else if let Some(token) = parse_decimal(token)? { + tokens.push(token); + } else if let Some(token) = parse_label(token, module)? { + tokens.push(token); + } else if let Some(token) = parse_symbol(token, module)? { + tokens.push(token); + } else { + return Err(AssembleError::Generic); + } + } + } + + println!("{:#?}", tokens); + + Ok(tokens) +} +pub fn parse_register(token: &str) -> Result, AssembleError> { + Ok(Register::try_from(token).map(Token::Register).ok()) +} + +pub fn parse_opcode(token: &str) -> Result, AssembleError> { + if Opcode::OPCODES.contains(&token) { + Ok(Some(Token::Opcode(Opcode::from_str(token).expect( + "Opcode::from_str failed for a valid opcode token", + )))) + } else { + Ok(None) + } +} + +pub fn parse_hex(token: &str) -> Result, AssembleError> { + if (token.len() < 3) | !token.starts_with("0x") { + return Ok(None); + } + + let Some(lit) = &token.get(2..) else { + return Err(AssembleError::InvalidArg); + }; + + u32::from_str_radix(lit, 16).map_or(Err(AssembleError::Generic), |value| { + Ok(Some(Token::Immediate(value))) + }) +} + +pub fn parse_octal(token: &str) -> Result, AssembleError> { + if (token.len() < 3) | !token.starts_with("0o") { + return Ok(None); + } + + let Some(lit) = &token.get(2..) else { + return Err(AssembleError::InvalidArg); + }; + + u32::from_str_radix(lit, 8).map_or(Err(AssembleError::Generic), |value| { + Ok(Some(Token::Immediate(value))) + }) +} + +pub fn parse_binary(token: &str) -> Result, AssembleError> { + if (token.len() < 3) | !token.starts_with("0b") { + return Ok(None); + } + + let Some(lit) = &token.get(2..) else { + return Err(AssembleError::InvalidArg); + }; + + u32::from_str_radix(lit, 2).map_or(Err(AssembleError::Generic), |value| { + Ok(Some(Token::Immediate(value))) + }) +} + +pub fn parse_decimal(token: &str) -> Result, AssembleError> { + let Ok(tok) = token.parse::() else { + return Ok(None); + }; + + Ok(Some(Token::Immediate(tok))) +} + +pub fn parse_label(token: &str, module: u64) -> Result, AssembleError> { + if token.ends_with(':') { + Ok(Some(Token::Symbol(Symbol { + name: token[0..token.len() - 1].to_string(), + module: Module::Resolved(module), + }))) + } else { + Ok(None) + } +} + +pub fn parse_symbol(token: &str, module: u64) -> Result, AssembleError> { + let Some(tokc) = token.chars().next() else { + return Err(AssembleError::Generic); // TODO: What is this error? + }; + + if tokc.is_numeric() { + return Ok(None); + } + + let mut split = token.splitn(2, "::"); + let Some(symbol1) = split.next() else { + return Err(AssembleError::InvalidArg); + }; + let symbol1 = symbol1.to_string(); + + if let Some(symbol2) = split.next() { + Ok(Some(Token::Symbol(Symbol { + name: symbol2.to_string(), + module: Module::Unresolved(symbol1), + }))) + } else { + Ok(Some(Token::Symbol(Symbol { + name: symbol1, + module: Module::Resolved(module), + }))) + } +} diff --git a/assembler/src/assembler/macros.rs b/assembler/src/assembler/macros.rs new file mode 100644 index 0000000..592ef32 --- /dev/null +++ b/assembler/src/assembler/macros.rs @@ -0,0 +1,138 @@ +//! Macros used throughout the assembler + +use crate::assembler::model::{Node, Opcode, Symbol, Token}; +/// Parse DSA assembly code with optional formatting +/// +/// # Examples +/// ``` +/// // With formatting: +/// let nodes = dsa!(hash, "mov r1, {}", 42)?; +/// +/// // Without formatting: +/// let nodes = dsa!(hash, "mov r1, 42")?; +/// ``` +#[macro_export] +macro_rules! dsa { + // Version with formatting arguments + ($hash:expr, $input:expr, $($args:expr),+) => {{ + let input = format!($input, $($args),+); + let tokens = $crate::lexer::lexer(input, $hash)?; + let parsed = $crate::parser::Parser::parse_nodes(tokens)?; + parsed + }}; + // Version without formatting + ($hash:expr, $input:expr) => {{ + let input = String::from($input); + let tokens = $crate::lexer::lexer(input, $hash)?; + let parsed = $crate::parser::Parser::parse_nodes(tokens)?; + parsed + }}; +} + +/// Creates a new Node with the given symbol, opcode, and tokens +#[macro_export] +macro_rules! node { + ($symbol: expr, $opcode: expr, args: $tokens: expr) => { + $crate::assembler::model::Node::new($symbol.clone(), $opcode.clone(), $tokens.clone()) + }; + + ($symbol: expr, $opcode: expr, $($tokens: expr),+) => { + $crate::assembler::model::Node::new( + $symbol.clone(), + $opcode.clone(), + vec![$(node!(@convert_token $tokens)),+] + ) + }; + + ($symbol: expr, $opcode: expr) => { + $crate::assembler::model::Node::new( + $symbol.clone(), + $opcode.clone(), + Vec::new() + ) + }; + + (@convert_token $token: literal) => { + $crate::assembler::model::Token::Immediate($token) + }; + + (@convert_token $token: expr) => { + $token.clone() + }; +} + +/// Extracts a specific token type from a token +#[macro_export] +macro_rules! expect_token { + ($token:expr, Symbol) => { + match $token { + $crate::assembler::model::Token::Symbol(value) => Ok(value.clone()), + other => Err($crate::assembler::AssembleError::UnexpectedToken( + other.clone(), + $crate::assembler::model::TokenType::Symbol, + )), + } + }; + ($token:expr, Register) => { + match $token { + $crate::assembler::model::Token::Register(value) => Ok(value.clone()), + other => Err($crate::assembler::AssembleError::UnexpectedToken( + other.clone(), + $crate::assembler::model::TokenType::Register, + )), + } + }; + ($token:expr, Immediate) => { + match $token { + $crate::assembler::model::Token::Immediate(value) => Ok(value.clone()), + other => Err($crate::assembler::AssembleError::UnexpectedToken( + other.clone(), + $crate::assembler::model::TokenType::Immediate, + )), + } + }; + ($token:expr, StringLit) => { + match $token { + $crate::assembler::model::Token::StringLit(value) => Ok(value.clone()), + other => Err($crate::assembler::AssembleError::UnexpectedToken( + other.clone(), + $crate::assembler::model::TokenType::StringLit, + )), + } + }; + ($token:expr, Opcode) => { + match $token { + $crate::assembler::model::Token::Opcode(value) => Ok(value.clone()), + other => Err($crate::assembler::AssembleError::UnexpectedToken( + other.clone(), + $crate::assembler::model::TokenType::Opcode, + )), + } + }; +} + +/// Checks if a token matches any of the specified types +#[macro_export] +macro_rules! expect_type { + ($token:expr, $($variant:ident),+) => {{ + let token = $token; + match &token { + $( + $crate::assembler::model::Token::$variant(_) => Ok(token.clone()), + )+ + other => { + let expected_type = expect_type!(@get_first_type $($variant),+); + Err($crate::assembler::AssembleError::UnexpectedToken( + other.clone().clone(), + expected_type, + )) + } + } + }}; + + (@get_first_type Symbol $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Symbol }; + (@get_first_type Register $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Register }; + (@get_first_type Immediate $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Immediate }; + (@get_first_type StringLit $(, $rest:ident)*) => { $crate::assembler::model::TokenType::StringLit }; + (@get_first_type Opcode $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Opcode }; +} diff --git a/assembler/src/assembler/mod.rs b/assembler/src/assembler/mod.rs new file mode 100644 index 0000000..26fd801 --- /dev/null +++ b/assembler/src/assembler/mod.rs @@ -0,0 +1,266 @@ +#![allow(dead_code, unused)] + +use std::{ + collections::HashSet, + fmt, fs, + hash::{DefaultHasher, Hash, Hasher}, + path::{Path, PathBuf}, + sync::{Arc, Mutex, mpsc}, + thread, +}; + +use common::prelude::Instruction; + +// TODO: Use an actual logging or tracing library for pretty (scoped) output. +fn log(message: &str) { + println!("\x1b[32mINFO:\x1b[0m {message}"); +} + +// Module declarations +#[macro_use] +pub mod macros; + +#[allow(clippy::module_inception)] +pub mod assembler; +pub mod codegen; +pub mod expand; +pub mod lexer; +pub mod model; +pub mod parser; +pub mod resolver; + +// Re-exports +pub use self::{ + codegen::codegen, + expand::expand_pseudo_ops, + lexer::lexer, + model::{Module, Node, Opcode, Symbol, Token, TokenType}, + parser::{Parser, Program}, + resolver::{create_sections, resolve_dependencies, resolve_symbols}, +}; + +use crate::util::logging::{Entry, Logger}; + +pub struct CompilerEngine { + result_tx: mpsc::Sender, AssembleError>>, + result_rx: Option, AssembleError>>>, + is_running: bool, +} + +impl CompilerEngine { + #[must_use] + pub fn new() -> Self { + let (tx, rx) = mpsc::channel(); + Self { + result_tx: tx, + result_rx: Some(rx), + is_running: false, + } + } + + /// Start the compilation process in a separate thread + pub fn start_compilation(&mut self, src: &Path) { + if self.is_running { + return; + } + + let src = src.to_path_buf(); + let tx = self.result_tx.clone(); + + thread::spawn(move || { + let result = assemble(&src); + tx.send(result) + .expect("Failed to send compilation result from worker thread"); + }); + + self.is_running = true; + } + + /// Check if compilation is complete and get the result + pub fn try_get_result(&mut self) -> Option, AssembleError>> { + if !self.is_running { + return None; + } + + match self + .result_rx + .as_ref() + .expect("result_rx should be Some while compilation is running") + .try_recv() + { + Ok(result) => { + self.is_running = false; + Some(result) + } + Err(mpsc::TryRecvError::Empty) => None, + Err(mpsc::TryRecvError::Disconnected) => { + self.is_running = false; + Some(Err(AssembleError::Generic)) + } + } + } + + /// Block until compilation is complete and return the result + pub fn wait_for_result(&mut self) -> Result, AssembleError> { + if !self.is_running { + return Err(AssembleError::Generic); + } + + if let Ok(result) = self + .result_rx + .take() + .expect("result_rx should be Some while waiting for compilation result") + .recv() + { + self.is_running = false; + result + } else { + self.is_running = false; + Err(AssembleError::Generic) + } + } +} + +fn assemble(src: &Path) -> Result, AssembleError> { + let mut modules = HashSet::new(); + let mut program = Program::new(); + + let hash = quick_hash(src); + + if modules.contains(&hash) { + return Ok(vec![]); + } + + prepare_dependency(src, &mut modules, &mut program)?; + + let mut nodes = program.nodes.clone(); + + create_sections(&mut nodes)?; + resolve_symbols(&mut nodes)?; + + let instructions = codegen(nodes)?; + Ok(instructions) +} + +impl Default for CompilerEngine { + fn default() -> Self { + Self::new() + } +} + +fn prepare_dependency( + path: &Path, + modules: &mut HashSet, + program: &mut Program, +) -> Result<(), AssembleError> { + let filename = path + .file_name() + .and_then(|n| n.to_str()) + .expect("Failed to get file name from path"); + + if let Ok(path) = path.canonicalize() { + log(&format!( + "{:20} {:20} [{}]", + "Building", + filename, + path.display() + )); + } + + let src = fs::read_to_string(path) + .map_err(|_| AssembleError::InvalidFile(path.to_path_buf()))?; + let file_hash = quick_hash(path); + + log(&format!("{:20} {:20}", "Tokenising", filename)); + let tokens = lexer::lexer(src, file_hash)?; + + log(&format!("{:20} {:20}", "Parsing", filename)); + let parsed = Parser::parse_nodes(tokens)?; + + log(&format!("{:20} {:20}", "Resolving Deps", filename)); + // Get the parent directory of the source file to use as the base directory + let base_dir = path + .parent() + .ok_or_else(|| AssembleError::InvalidFile(path.to_path_buf()))?; + let mut nodes = expand_pseudo_ops(parsed, file_hash)?; + nodes = resolve_dependencies(nodes, base_dir)?; + + let deps = Parser::get_dependencies(&nodes, path)?; + + log(&format!( + "{:20} {:20}", + "Expanding PseudoInstructions", filename + )); + + // add a section instruction + nodes.insert( + 0, + node!(None, Opcode::Segment, Token::Immediate(file_hash as u32)), + ); + + for n in &nodes { + println!("{n}"); + } + + program.add_module(nodes); + + for dep in deps { + log(&format!( + "{:20} {:20}", + "Including", + dep.file_name() + .and_then(|f| f.to_str()) + .expect("Dependency path has no file name or is not valid UTF-8") + )); + + let dep_hash = quick_hash(&dep); + if modules.insert(dep_hash) { + prepare_dependency(dep.as_path(), modules, program)?; + } + } + + Ok(()) +} + +#[derive(Debug, Clone)] +pub enum AssembleError { + Generic, + UnexpectedEof, + InvalidFile(PathBuf), + UnexpectedToken(Token, TokenType), + InvalidArg, + UndefinedSymbol(Symbol), + /// Contains the nth element missing from the instruction. + MissingArgument(u8), +} + +impl fmt::Display for AssembleError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Generic => write!(f, "Generic error"), + Self::UnexpectedToken(tok, expected) => { + write!(f, "Unexpected token {tok:?}, expected {expected:?}") + } + Self::UnexpectedEof => write!(f, "Unexpected end of file"), + Self::InvalidFile(path) => write!(f, "Invalid file `{}`", path.display()), + Self::InvalidArg => write!(f, "Invalid argument"), + Self::UndefinedSymbol(symbol) => { + write!(f, "Undefined symbol {symbol}") + } + Self::MissingArgument(n) => { + write!(f, "Missing argument #{n} from instruction arguments.") + } + } + } +} + +fn quick_hash(value: &Path) -> u64 { + let mut hasher = DefaultHasher::new(); + value + .canonicalize() + .expect("Failed to canonicalize path for quick_hash") + .to_str() + .hash(&mut hasher); + + hasher.finish() +} diff --git a/assembler/src/assembler/model.rs b/assembler/src/assembler/model.rs new file mode 100644 index 0000000..059884d --- /dev/null +++ b/assembler/src/assembler/model.rs @@ -0,0 +1,438 @@ +use std::{fmt, str::FromStr}; + +use common::prelude::Register; + +use crate::assembler::AssembleError; + +#[derive(Debug, Clone)] +pub struct Node { + pub symbol: Option, + pub opcode: Opcode, + pub tokens: Vec, +} + +impl Node { + #[must_use] + pub const fn new(symbol: Option, opcode: Opcode, tokens: Vec) -> Self { + Self { + symbol, + opcode, + tokens, + } + } + + #[must_use] + pub fn label(&self) -> Option { + self.symbol.clone() + } + + #[must_use] + pub const fn opcode(&self) -> Opcode { + self.opcode + } + + #[must_use] + pub fn args(&self) -> Vec { + self.tokens.clone() + } + + pub fn arg(&self, index: usize) -> Result { + self.args() + .get(index) + .cloned() + .ok_or(AssembleError::InvalidArg) + } +} + +impl fmt::Display for Node { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let symbol = self + .label() + .as_ref() + .map_or_else(String::new, |symbol| format!("{symbol}:\n")); + + let args = self + .args() + .into_iter() + .map(|arg| arg.to_string()) + .collect::>() + .join(" "); + + write!( + f, + "\x1b[93m{} \t\x1b[94m{} \x1b[37m{} \x1b[0m", + symbol, + self.opcode(), + args, + ) + } +} + +impl fmt::Display for Symbol { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} [ID:{}]", self.name, self.module) + } +} + +impl fmt::Display for Module { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Unresolved(name) => write!(f, "{name}"), + Self::Resolved(name) => write!(f, "{name}"), + } + } +} + +impl fmt::Display for Opcode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Nop => write!(f, "nop"), + Self::Mov => write!(f, "mov"), + Self::Movs => write!(f, "movs"), + Self::Ldb => write!(f, "ldb"), + Self::Ldbs => write!(f, "ldbs"), + Self::Ldh => write!(f, "ldh"), + Self::Ldhs => write!(f, "ldhs"), + Self::Ldw => write!(f, "ldw"), + Self::Stb => write!(f, "stb"), + Self::Sth => write!(f, "sth"), + Self::Stw => write!(f, "stw"), + Self::Lli => write!(f, "lli"), + Self::Lui => write!(f, "lui"), + Self::Jmp => write!(f, "jmp"), + Self::Jeq => write!(f, "jeq"), + Self::Jne => write!(f, "jne"), + Self::Jgt => write!(f, "jgt"), + Self::Jge => write!(f, "jge"), + Self::Jlt => write!(f, "jlt"), + Self::Jle => write!(f, "jle"), + Self::Cmp => write!(f, "cmp"), + Self::Inc => write!(f, "inc"), + Self::Dec => write!(f, "dec"), + Self::Shl => write!(f, "shl"), + Self::Shr => write!(f, "shr"), + Self::Add => write!(f, "add"), + Self::Sub => write!(f, "sub"), + Self::And => write!(f, "and"), + Self::Or => write!(f, "or"), + Self::Not => write!(f, "not"), + Self::Xor => write!(f, "xor"), + Self::Nand => write!(f, "nand"), + Self::Nor => write!(f, "nor"), + Self::Xnor => write!(f, "xnor"), + Self::Int => write!(f, "int"), + Self::Irt => write!(f, "irt"), + Self::Hlt => write!(f, "hlt"), + Self::AddI => write!(f, "addi"), + Self::SubI => write!(f, "subi"), + Self::Db => write!(f, "db"), + Self::Dh => write!(f, "dh"), + Self::Dw => write!(f, "dw"), + Self::Resb => write!(f, "resb"), + Self::Resh => write!(f, "resh"), + Self::Resw => write!(f, "resw"), + Self::Push => write!(f, "push"), + Self::Pop => write!(f, "pop"), + Self::Lwi => write!(f, "lwi"), + Self::Call => write!(f, "call"), + Self::Return => write!(f, "return"), + Self::Pusha => write!(f, "pusha"), + Self::Popa => write!(f, "popa"), + + // meta instructions + Self::Include => write!(f, "include"), + Self::Data => write!(f, "data"), + Self::Segment => write!(f, "[SEGMENT]"), + } + } +} + +#[derive(Debug, Clone, Eq)] +pub struct Symbol { + pub name: String, + pub module: Module, +} + +impl std::hash::Hash for Symbol { + fn hash(&self, state: &mut H) { + self.name.hash(state); + self.module.hash(state); + } +} + +impl PartialEq for Symbol { + fn eq(&self, other: &Self) -> bool { + self.name == other.name && self.module == other.module + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Module { + Resolved(u64), + Unresolved(String), +} + +#[derive(Debug, Clone)] +pub enum Token { + Symbol(Symbol), + Register(Register), + Immediate(u32), + StringLit(String), + Opcode(Opcode), +} + +impl fmt::Display for Token { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Symbol(symbol) => write!(f, "{}", symbol), + Self::Register(register) => write!(f, "{}", register), + Self::Immediate(immediate) => write!(f, "{}", immediate), + Self::StringLit(string_lit) => write!(f, "{}", string_lit), + Self::Opcode(opcode) => write!(f, "{}", opcode), + } + } +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum TokenType { + Symbol, + Register, + Immediate, + StringLit, + Opcode, +} + +impl TokenType { + #[must_use] + pub const fn from_token(token: &Token) -> Self { + match token { + Token::Symbol(_) => Self::Symbol, + Token::Register(_) => Self::Register, + Token::Immediate(_) => Self::Immediate, + Token::StringLit(_) => Self::StringLit, + Token::Opcode(_) => Self::Opcode, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Opcode { + // Real instructions (0x00-0x26) + Nop, + Mov, + Movs, + Ldb, + Ldbs, + Ldh, + Ldhs, + Ldw, + Stb, + Sth, + Stw, + Lli, + Lui, + Jmp, + Jeq, + Jne, + Jgt, + Jge, + Jlt, + Jle, + Cmp, + Inc, + Dec, + Shl, + Shr, + Add, + Sub, + And, + Or, + Not, + Xor, + Nand, + Nor, + Xnor, + Int, + Irt, + Hlt, + AddI, + SubI, + + // Pseudo-instructions + Db, + Dh, + Dw, + Resb, + Resh, + Resw, + Push, + Pop, + Pusha, + Popa, + Lwi, + Call, + Return, + + // meta instructions (these aren't present in the binary as instructions) + Include, + Data, + Segment, +} + +#[derive(Debug)] +pub enum OpcodeFromStrError { + InvalidRegister(&'static str), + InvalidOpcode(String), +} + +impl std::fmt::Display for OpcodeFromStrError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidRegister(reg) => write!(f, "register does not exist: {reg}"), + Self::InvalidOpcode(op) => write!(f, "instruction does not exist: {op}"), + } + } +} + +impl std::error::Error for OpcodeFromStrError {} + +impl FromStr for Opcode { + type Err = OpcodeFromStrError; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "nop" => Ok(Self::Nop), + "mov" => Ok(Self::Mov), + "movs" => Ok(Self::Movs), + "ldb" => Ok(Self::Ldb), + "ldbs" => Ok(Self::Ldbs), + "ldh" => Ok(Self::Ldh), + "ldhs" => Ok(Self::Ldhs), + "ldw" => Ok(Self::Ldw), + "stb" => Ok(Self::Stb), + "sth" => Ok(Self::Sth), + "stw" => Ok(Self::Stw), + "lli" => Ok(Self::Lli), + "lui" => Ok(Self::Lui), + "jmp" => Ok(Self::Jmp), + "jeq" => Ok(Self::Jeq), + "jne" => Ok(Self::Jne), + "jgt" => Ok(Self::Jgt), + "jge" => Ok(Self::Jge), + "jlt" => Ok(Self::Jlt), + "jle" => Ok(Self::Jle), + "cmp" => Ok(Self::Cmp), + "inc" => Ok(Self::Inc), + "dec" => Ok(Self::Dec), + "shl" => Ok(Self::Shl), + "shr" => Ok(Self::Shr), + "add" => Ok(Self::Add), + "sub" => Ok(Self::Sub), + "and" => Ok(Self::And), + "or" => Ok(Self::Or), + "not" => Ok(Self::Not), + "xor" => Ok(Self::Xor), + "nand" => Ok(Self::Nand), + "nor" => Ok(Self::Nor), + "xnor" => Ok(Self::Xnor), + "int" => Ok(Self::Int), + "irt" => Ok(Self::Irt), + "hlt" => Ok(Self::Hlt), + "addi" => Ok(Self::AddI), + "subi" => Ok(Self::SubI), + "db" => Ok(Self::Db), + "dh" => Ok(Self::Dh), + "dw" => Ok(Self::Dw), + "resb" => Ok(Self::Resb), + "resh" => Ok(Self::Resh), + "resw" => Ok(Self::Resw), + "push" => Ok(Self::Push), + "pop" => Ok(Self::Pop), + "lwi" => Ok(Self::Lwi), + "include" => Ok(Self::Include), + "call" => Ok(Self::Call), + "return" => Ok(Self::Return), + "pusha" => Ok(Self::Pusha), + "popa" => Ok(Self::Popa), + _ => Err(OpcodeFromStrError::InvalidOpcode(s.to_string())), + } + } +} + +impl Opcode { + pub const OPCODES: &[&str] = &[ + // Real instructions (0x00-0x26) + "nop", "mov", "movs", "ldb", "ldbs", "ldh", "ldhs", "ldw", "stb", "sth", "stw", + "lli", "lui", "jmp", "jeq", "jne", "jgt", "jge", "jlt", "jle", "cmp", "inc", + "dec", "shl", "shr", "add", "sub", "and", "or", "not", "xor", "nand", "nor", + "xnor", "int", "irt", "hlt", "addi", "subi", // Pseudo-instructions + "db", "dh", "dw", "resb", "resh", "resw", "push", "pop", "lwi", "call", "return", + "pusha", "popa", // meta instructions + "include", + ]; + + #[must_use] + pub const fn to_opcode_value(&self) -> Option { + match self { + Self::Nop => Some(0x00), + Self::Mov => Some(0x01), + Self::Movs => Some(0x02), + Self::Ldb => Some(0x03), + Self::Ldbs => Some(0x04), + Self::Ldh => Some(0x05), + Self::Ldhs => Some(0x06), + Self::Ldw => Some(0x07), + Self::Stb => Some(0x08), + Self::Sth => Some(0x09), + Self::Stw => Some(0x0A), + Self::Lli => Some(0x0B), + Self::Lui => Some(0x0C), + Self::Jmp => Some(0x0D), + Self::Jeq => Some(0x0E), + Self::Jne => Some(0x0F), + Self::Jgt => Some(0x10), + Self::Jge => Some(0x11), + Self::Jlt => Some(0x12), + Self::Jle => Some(0x13), + Self::Cmp => Some(0x14), + Self::Inc => Some(0x15), + Self::Dec => Some(0x16), + Self::Shl => Some(0x17), + Self::Shr => Some(0x18), + Self::Add => Some(0x19), + Self::Sub => Some(0x1A), + Self::And => Some(0x1B), + Self::Or => Some(0x1C), + Self::Not => Some(0x1D), + Self::Xor => Some(0x1E), + Self::Nand => Some(0x1F), + Self::Nor => Some(0x20), + Self::Xnor => Some(0x21), + Self::Int => Some(0x22), + Self::Irt => Some(0x23), + Self::Hlt => Some(0x24), + Self::AddI => Some(0x25), + Self::SubI => Some(0x26), + Self::Segment => Some(0x27), + + // Pseudo-instructions don't have opcode values + _ => None, + } + } + + #[must_use] + pub const fn is_pseudo_instruction(&self) -> bool { + matches!( + self, + Self::Db + | Self::Dh + | Self::Dw + | Self::Resb + | Self::Resh + | Self::Resw + | Self::Push + | Self::Pop + | Self::Lwi + ) + } +} diff --git a/assembler/src/assembler/parser.rs b/assembler/src/assembler/parser.rs new file mode 100644 index 0000000..7ac26b2 --- /dev/null +++ b/assembler/src/assembler/parser.rs @@ -0,0 +1,368 @@ +use std::path::{Path, PathBuf}; + +use crate::{assembler::AssembleError, expect_token, expect_type, node}; + +use crate::assembler::model::{Node, Opcode, Token}; +use common::prelude::*; + +pub struct Parser { + tokens: Vec, + nodes: Vec, +} + +#[derive(Debug)] +pub struct Program { + pub nodes: Vec, +} + +impl Program { + #[must_use] + pub const fn new() -> Self { + Self { nodes: vec![] } + } + + pub fn add_module(&mut self, module: Vec) { + self.nodes.extend(module); + } + + pub fn parser(&mut self) -> Parser { + Parser { + tokens: vec![], + nodes: self.nodes.clone(), + } + } +} + +impl Default for Program { + fn default() -> Self { + Self::new() + } +} + +impl Parser { + pub fn parse_nodes(tokens: Vec) -> Result, AssembleError> { + let mut self_ = Self { + tokens: tokens.into_iter().rev().collect(), + nodes: vec![], + }; + + while !self_.tokens.is_empty() { + let ins = self_.parse_instruction()?; + self_.nodes.push(ins); + } + + Ok(self_.nodes.clone()) + } + + pub fn get_dependencies( + nodes: &Vec, + source_path: &Path, + ) -> Result, AssembleError> { + let mut dependencies = Vec::new(); + // Get the parent directory of the source file to use as the base directory + let base_dir = source_path + .parent() + .ok_or_else(|| AssembleError::InvalidFile(source_path.to_path_buf()))?; + + for node in nodes { + if node.opcode() == Opcode::Include { + let path_str = expect_token!( + node.args().get(1).ok_or(AssembleError::Generic)?, + StringLit + )?; + let path = PathBuf::from(path_str); + + // If the path is not absolute, make it relative to the base directory + let full_path = if path.is_absolute() { + path + } else { + base_dir.join(path) + }; + + dependencies.push(full_path); + } + } + Ok(dependencies) + } + + #[expect(clippy::too_many_lines, clippy::cognitive_complexity)] + fn parse_instruction(&mut self) -> Result { + if self.tokens.is_empty() { + unreachable!(); + } + + // check if the Node starts with a label + let label = expect_token!(self.peek_next()?, Symbol).ok(); + if label.is_some() { + self.tokens.pop(); + } + + let opcode = expect_token!(self.next()?, Opcode)?; + let args: Vec; + + match opcode { + // R-type instructions + Opcode::Mov | Opcode::Movs => { + let reg1 = expect_type!(self.next()?, Register, Symbol)?; + let reg2 = expect_type!(self.next()?, Register, Symbol)?; + args = vec![reg1, reg2]; + } + + Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw => { + let base = expect_type!(self.next()?, Register, Symbol)?; + let dest = expect_type!(self.next()?, Register)?; + + let mut offset = Token::Immediate(0); + if let Ok(next) = self.peek_next() + && expect_type!(next, Immediate).is_ok() { + offset = self.next()?; + } + + args = vec![base, dest, offset]; + } + Opcode::Stb | Opcode::Sth | Opcode::Stw => { + let base = expect_type!(self.next()?, Register)?; + let dest = expect_type!(self.next()?, Register, Symbol)?; + let mut offset = Token::Immediate(0); + if let Ok(next) = self.peek_next() + && expect_type!(next, Immediate).is_ok() { + offset = self.next()?; + } + args = vec![base, dest, offset]; + } + + Opcode::Add + | Opcode::Sub + | Opcode::And + | Opcode::Or + | Opcode::Xor + | Opcode::Nand + | Opcode::Nor + | Opcode::Xnor => { + let src1 = expect_type!(self.next()?, Register, Symbol)?; + let src2 = expect_type!(self.next()?, Register, Symbol)?; + let dest = expect_type!(self.next()?, Register, Symbol)?; + args = vec![src1, src2, dest]; + } + + Opcode::Not | Opcode::Cmp => { + let reg1 = expect_type!(self.next()?, Register, Symbol)?; + let reg2 = expect_type!(self.next()?, Register, Symbol)?; + args = vec![reg1, reg2]; + } + + Opcode::Shl | Opcode::Shr => { + let reg = expect_type!(self.next()?, Register, Symbol)?; + let num = expect_type!(self.next()?, Immediate)?; + args = vec![reg, num]; + } + + Opcode::Inc | Opcode::Dec => { + let reg = expect_type!(self.next()?, Register, Symbol)?; + args = vec![reg]; + } + + Opcode::Include => { + let mod_name = expect_type!(self.next()?, Symbol)?; + let path = expect_type!(self.next()?, StringLit)?; + args = vec![mod_name, path]; + } + + // J-type instructions + Opcode::Jmp + | Opcode::Jeq + | Opcode::Jne + | Opcode::Jgt + | Opcode::Jge + | Opcode::Jlt + | Opcode::Jle => { + let imm = expect_type!(self.next()?, Immediate, Symbol)?; + let offset = match self.peek_next() { + Ok(token) => { + if expect_type!(token, Register).is_ok() { + self.next()? + } else { + Token::Register(Register::Zero) + } + } + Err(_) => Token::Register(Register::Zero), + }; + args = vec![imm, offset]; + } + + Opcode::Call => { + let addr = expect_type!(self.next()?, Symbol)?; + args = vec![addr]; + } + + // I-type instructions + Opcode::Lui | Opcode::Lli | Opcode::Lwi => { + let imm = expect_type!(self.next()?, Immediate, Symbol)?; + let reg = expect_type!(self.next()?, Register)?; + args = vec![imm, reg]; + } + + // Immediate Arithmetic + Opcode::AddI | Opcode::SubI => { + let reg = expect_type!(self.next()?, Register)?; + let imm = expect_type!(self.next()?, Immediate)?; + let reg2 = if expect_type!(self.peek_next()?, Register).is_ok() { + self.next()? + } else { + reg.clone() + }; + args = vec![reg, imm, reg2]; + } + + // D-type pseudoinstructions (data definition) + Opcode::Resb | Opcode::Resh | Opcode::Resw => { + let name = expect_type!(self.next()?, Symbol)?; + let num = expect_type!(self.next()?, Immediate)?; + args = vec![name, num]; + } + + Opcode::Db | Opcode::Dh | Opcode::Dw => { + args = self.parse_data_definition(opcode)?; + } + + // E-type pseudoinstructions (stack operations) + Opcode::Push | Opcode::Pop => { + let reg = expect_type!(self.next()?, Register, Symbol)?; + args = vec![reg]; + } + + Opcode::Pusha | Opcode::Popa => { + let count = + expect_type!(self.next()?, Immediate).unwrap_or(Token::Immediate(8)); + args = vec![count]; + } + + // Special instructions + Opcode::Int => { + let val = expect_type!(self.next()?, Immediate)?; + args = vec![val]; + } + + // Instructions with no arguments + Opcode::Hlt | Opcode::Nop | Opcode::Irt | Opcode::Return => { + args = vec![]; + } + + Opcode::Data | Opcode::Segment => { + return Err(AssembleError::Generic); + } + } + + Ok(node!(label, opcode, args: args)) + } + + fn parse_data_definition( + &mut self, + opcode: Opcode, + ) -> Result, AssembleError> { + let mut values = Vec::new(); + + let name = expect_type!(self.next()?, Symbol)?; + values.push(name); + + match opcode { + Opcode::Db => { + // db can take string literals or u8 immediates + while !self.tokens.is_empty() { + let token = self + .tokens + .last() + .expect("Expected a token for data definition, but found none"); + + match token { + Token::StringLit(_) => { + values.push(self.tokens.pop().expect( + "Expected a token for data definition, but found none", + )); + } + Token::Immediate(val) if u8::try_from(*val).is_ok() => { + values.push(self.tokens.pop().expect( + "Expected a token for data definition, but found none", + )); + } + _ => break, + } + } + } + + Opcode::Dh => { + // dh can take u16 immediates + while !self.tokens.is_empty() { + let token = self + .tokens + .last() + .expect("Expected a token for data definition, but found none"); + + match token { + Token::StringLit(_) => { + values.push(self.tokens.pop().expect( + "Expected a token for data definition, but found none", + )); + } + Token::Immediate(val) if u16::try_from(*val).is_ok() => { + values.push(self.tokens.pop().expect( + "Expected a token for data definition, but found none", + )); + } + _ => break, + } + } + } + + Opcode::Dw => { + // dw can take u32 immediates + while !self.tokens.is_empty() { + match self + .tokens + .last() + .expect("Expected a token for data definition, but found none") + { + Token::StringLit(_) => { + values.push(self.tokens.pop().expect( + "Expected a token for data definition, but found none", + )); + } + Token::Immediate(val) => { + values.push(self.tokens.pop().expect( + "Expected a token for data definition, but found none", + )); + } + _ => break, + } + } + } + + _ => unreachable!(), + } + + Ok(values) + } + + fn next(&mut self) -> Result { + if self.tokens.is_empty() { + Err(AssembleError::UnexpectedEof) + } else { + Ok(self + .tokens + .pop() + .expect("tokens vector was unexpectedly empty in next()")) + } + } + + fn peek_next(&self) -> Result { + if self.tokens.is_empty() { + Err(AssembleError::UnexpectedEof) + } else { + Ok(self + .tokens + .last() + .expect("peek_next called on empty tokens vector") + .clone()) + } + } +} diff --git a/assembler/src/assembler/resolver.rs b/assembler/src/assembler/resolver.rs new file mode 100644 index 0000000..626a045 --- /dev/null +++ b/assembler/src/assembler/resolver.rs @@ -0,0 +1,156 @@ +use std::{ + collections::HashMap, + fs::canonicalize, + path::{Path, PathBuf}, +}; + +use common::prelude::Register; + +use crate::assembler::quick_hash; +use crate::assembler::{ + log, + model::{Module, Node, Opcode, Symbol, Token}, +}; +use crate::{assembler::AssembleError, node}; + +pub fn resolve_symbols(nodes: &mut [Node]) -> Result<(), AssembleError> { + let symbol_table = generate_symbol_table(nodes); + + for node in nodes.iter_mut() { + match node.opcode() { + Opcode::Jmp + | Opcode::Jeq + | Opcode::Jne + | Opcode::Jgt + | Opcode::Jge + | Opcode::Jlt + | Opcode::Jle + | Opcode::Lli + | Opcode::Lui => { + if let Token::Symbol(symbol) = node + .arg(0) + .expect("Expected argument 0 for jump-like opcode") + { + if let Some(address) = symbol_table.get(&symbol) { + node.tokens[0] = Token::Immediate(*address); + } else { + return Err(AssembleError::UndefinedSymbol(symbol)); + } + } + } + _ => (), + } + } + + Ok(()) +} + +fn generate_symbol_table(nodes: &[Node]) -> HashMap { + let mut table = HashMap::new(); + + for (i, node) in nodes.iter().enumerate() { + if let Some(symbol) = node.label() { + table.insert(symbol, 4 * i as u32); + } + } + + table +} + +pub fn resolve_dependencies( + mut nodes: Vec, + base_dir: &Path, +) -> Result, AssembleError> { + // First we get a list of imports. + let mut dependencies = Vec::new(); + for node in &nodes { + if node.opcode() == Opcode::Include { + // we want the path, and the name + let name = if let Token::Symbol(name) = node + .arg(0) + .expect("Expected argument #0 for Include directive.") + { + name.name.clone() + } else { + unreachable!() + }; //node.2.get(0).unwrap() + + let Ok(Token::StringLit(path)) = node.arg(1) else { + unreachable!() + }; + + let full_path = base_dir.join(path); + let canonical_path = full_path + .canonicalize() + .map_err(|_| AssembleError::InvalidFile(full_path.clone()))?; + + let hash = quick_hash(&canonical_path); + + dependencies.push((name, hash)); + } + } + + let mut changes = Vec::<(u32, u32, Symbol)>::new(); + // now we resolve the symbols on all the nodes + // we need to check all operands for unresolved signals + for (i, node) in nodes.clone().iter().enumerate() { + let Node { + tokens: operands, .. + } = node; + for (j, token) in operands.iter().enumerate() { + if let Token::Symbol(symbol) = token { + for d in &dependencies { + if let Module::Unresolved(name) = symbol.module.clone() { + if name != d.0 { + continue; + } + + let symbol = Symbol { + name: symbol.name.clone(), + module: Module::Resolved(d.1), + }; + changes.push((i as u32, j as u32, symbol)); + } + } + } + } + } + + for (i, j, symbol) in changes { + nodes[i as usize].tokens[j as usize] = Token::Symbol(symbol); + } + + Ok(nodes) +} + +pub fn create_sections(nodes: &mut Vec) -> Result<(), AssembleError> { + let mut res = Vec::::with_capacity(nodes.len()); + + res.push(node!(None, Opcode::Segment, Token::Immediate(0))); + + for n in nodes.iter() { + if n.opcode() == Opcode::Data { + res.push(n.clone()); + } + } + + let start = res.len() + 1; + res.insert( + 0, + node!( + None, + Opcode::Jmp, + Token::Immediate(start as u32 * 4), + Token::Register(Register::Zero) + ), + ); + for n in nodes.iter() { + if !matches!(n.opcode(), Opcode::Data | Opcode::Include) { + res.push(n.clone()); + } + } + + *nodes = res; + + Ok(()) +} diff --git a/assembler/src/image_builder/mod.rs b/assembler/src/image_builder/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/assembler/src/lib.rs b/assembler/src/lib.rs new file mode 100644 index 0000000..d40c38a --- /dev/null +++ b/assembler/src/lib.rs @@ -0,0 +1,28 @@ +#![deny( + clippy::unwrap_used, + clippy::nursery, + clippy::perf, + clippy::pedantic, + clippy::complexity +)] +#![allow( + clippy::cast_possible_truncation, + clippy::missing_panics_doc, + clippy::missing_errors_doc, + clippy::match_wildcard_for_single_variants +)] + +pub mod assembler; +pub mod image_builder; +pub mod tooling; +mod util; + +pub mod prelude { + pub use crate::assembler::CompilerEngine; + pub use crate::image_builder; + pub use crate::tooling::brainf; + pub use crate::tooling::project; +} + +use num_cpus as _; +use threadpool as _; diff --git a/assembler/src/main.rs b/assembler/src/main.rs new file mode 100644 index 0000000..53d9d84 --- /dev/null +++ b/assembler/src/main.rs @@ -0,0 +1,64 @@ +use common as _; +use num_cpus as _; +use threadpool as _; + +use assembler::{ + prelude::*, + tooling::{brainf, project}, +}; +use std::{fs, io::Write, path::PathBuf}; + +fn main() { + // Parse command line arguments + let args: Vec = std::env::args().collect(); + + if args.len() == 2 && args[1] == "init" { + project::tool_libcreate(); + std::process::exit(0); + } + + if args.len() == 2 && args[1] == "brainf" { + let src = PathBuf::from("brainf.bf"); + let result = brainf::build(&src); + + let mut file = match fs::File::create("brainf.dsb") { + Err(e) => { + eprintln!("Failed to create output file: {e}"); + std::process::exit(1); + } + Ok(file) => file, + }; + + for instruction in result { + if let Err(e) = file.write(&instruction.encode().to_be_bytes()) { + eprintln!("Failed to write to output file: {e}"); + std::process::exit(1); + } + } + + std::process::exit(0); + } + + if args.len() != 5 || args[1] != "-i" || args[3] != "-o" { + eprintln!("Usage: {} -i input_path -o output_path", args[0]); + std::process::exit(1); + } + + let input_path = &args[2]; + let output_path = &args[4]; + let src = PathBuf::from(input_path); + + // Initialize the compiler engine + let mut compiler = CompilerEngine::new(); + compiler.start_compilation(&src); + + // Or block until done + let result = compiler.wait_for_result().unwrap(); + + for instruction in result { + if let Err(e) = fs::write(output_path, instruction.encode().to_be_bytes()) { + eprintln!("Failed to write to output file: {e}"); + std::process::exit(1); + } + } +} diff --git a/assembler/src/tooling/brainf.rs b/assembler/src/tooling/brainf.rs new file mode 100644 index 0000000..b8a0f77 --- /dev/null +++ b/assembler/src/tooling/brainf.rs @@ -0,0 +1,280 @@ +use std::{fs, path::Path}; + +use common::prelude::*; + +use crate::{ + assembler::{ + Module, Node, Opcode, Symbol, Token, codegen, create_sections, expand_pseudo_ops, + resolve_symbols, + }, + node, +}; + +#[must_use] +pub fn build(src: &Path) -> Vec { + let src = fs::read_to_string(src).expect("Failed to read source file"); + let mut nodes = parse(&src); + + // we need to expand pseudoinstructions etc now + nodes = expand_pseudo_ops(nodes, 0).expect("Failed to expand pseudo-operations"); + + create_sections(&mut nodes).expect("Failed to create sections"); + + for n in &nodes { + println!("{n}"); + } + + resolve_symbols(&mut nodes).expect("Failed to resolve symbols"); + + codegen(nodes).expect("Failed to generate code from nodes") +} + +#[must_use] +#[expect(clippy::too_many_lines)] +pub fn parse(src: &str) -> Vec { + let stack = Token::Immediate(0x10000); + let acc = Token::Register(Register::Acc); + let rga = Token::Register(Register::Rga); + + let bpr = Token::Register(Register::Bpr); + let spr = Token::Register(Register::Spr); + let mut nodes = Vec::::new(); + + // Define symbols + let print_start = Symbol { + name: "print".to_string(), + module: Module::Resolved(0), + }; + + let tokens = lex(src); + + // let _id = 0; + let mut idstack = Vec::::new(); + + nodes.extend(vec![ + // set up a stack + node!(None, Opcode::Lwi, stack, bpr), + node!(None, Opcode::Mov, bpr, spr), + // set up the data pointer + node!( + Some(Symbol { + name: "main".to_string(), + module: Module::Resolved(0) + }), + Opcode::Lwi, + Token::Immediate(0x30000), + rga + ), + ]); + + for (id, tok) in tokens.iter().enumerate() { + match tok { + BfToken::Inc => { + // inc acc + nodes.extend(vec![node!(None, Opcode::Inc, acc)]); + } + BfToken::Dec => { + // dec acc + nodes.extend(vec![node!(None, Opcode::Dec, acc)]); + } + BfToken::IncPtr => { + // stb acc, rga + // add rga, 4 + // ldb rga, acc + nodes.extend(vec![ + node!(None, Opcode::Stw, acc, rga, 0), + node!(None, Opcode::AddI, rga, 4, rga), + node!(None, Opcode::Ldw, rga, acc, 0), + ]); + } + BfToken::DecPtr => { + // stb acc, rga + // sub rga, 4 + // ldb rga, acc + nodes.extend(vec![ + node!(None, Opcode::Stw, acc, rga, 0), + node!(None, Opcode::SubI, rga, 4, rga), + node!(None, Opcode::Ldw, rga, acc, 0), + ]); + } + BfToken::Out => { + // push rga + // call print + // pop zero + nodes.extend(vec![ + node!(None, Opcode::Push, acc), + node!(None, Opcode::Call, Token::Symbol(print_start.clone())), + node!(None, Opcode::Pop, Token::Register(Register::Zero)), + ]); + } + BfToken::In => { + // Read a byte from input and store it at the current data pointer + // Assuming we have an input function mapped to a specific memory location or I/O port + nodes.extend(vec![ + // Read input (assuming input is mapped to memory address 0x40000) + node!(None, Opcode::Ldw, Token::Immediate(0x40000), acc, 0), + // Store the input byte at the current data pointer + ]); + } + BfToken::Forward => { + // Start of loop [ + let loop_start = format!("loop_start_{id}"); + let loop_end = format!("loop_end_{id}"); + + // Push the current position for the matching ] + idstack.push(id as u32); + + // Load current cell value and check if zero + nodes.extend(vec![ + // Compare with zero + node!(None, Opcode::Cmp, acc, Token::Register(Register::Zero)), + // If zero, jump to end of loop + node!( + None, + Opcode::Jeq, + Token::Symbol(Symbol { + name: loop_end, + module: Module::Resolved(0), + }), + Token::Register(Register::Zero) + ), + ]); + + // Add label for loop start + nodes.push(node!( + Some(Symbol { + name: loop_start, + module: Module::Resolved(0), + }), + Opcode::Nop + )); + } + BfToken::Back => { + // End of loop ] + if let Some(start_id) = idstack.pop() { + let loop_start = format!("loop_start_{start_id}"); + let loop_end = format!("loop_end_{start_id}"); + + // Jump back to the start of the loop + nodes.extend(vec![ + // Compare with zero + node!(None, Opcode::Cmp, acc, Token::Register(Register::Zero)), + // If not zero, jump back to start of loop + node!( + None, + Opcode::Jne, + Token::Symbol(Symbol { + name: loop_start, + module: Module::Resolved(0), + }), + Token::Register(Register::Zero) + ), + // Add label for loop end + node!( + Some(Symbol { + name: loop_end, + module: Module::Resolved(0), + }), + Opcode::Nop + ), + ]); + } else { + // Unmatched closing bracket - could add error handling here + eprintln!("Warning: Unmatched ']' at position {id}"); + } + } + } + } + + nodes.push(node!(None, Opcode::Hlt)); + + insert_lib(&mut nodes); + + nodes +} + +fn insert_lib(nodes: &mut Vec) { + let bpr = Token::Register(Register::Bpr); + let spr = Token::Register(Register::Spr); + let rg0 = Token::Register(Register::Rg0); + let rg1 = Token::Register(Register::Rg1); + + let print_start = Symbol { + name: "print".to_string(), + module: Module::Resolved(0), + }; + let current = Symbol { + name: "current".to_string(), + module: Module::Resolved(0), + }; + // set up the program framework. + nodes.extend(vec![ + // set display to 0x20000 + node!( + None, + Opcode::Dw, + Token::Symbol(current.clone()), + Token::Immediate(0x20000) + ), + // print function + // initialisation + node!(Some(print_start), Opcode::Push, bpr), + node!(None, Opcode::Mov, spr, bpr), + // function body + node!(None, Opcode::Ldw, bpr, rg0, Token::Immediate(8)), + node!( + None, + Opcode::Ldw, + Token::Symbol(current.clone()), // Load address of current + rg1, + Token::Immediate(0) + ), + node!(None, Opcode::Stb, rg0, rg1, Token::Immediate(0)), + node!(None, Opcode::AddI, rg1, Token::Immediate(1), rg1), + // function return according to spec. + node!( + None, + Opcode::Stw, + rg1, + Token::Symbol(current), // Store back to current + Token::Immediate(0) + ), + node!(None, Opcode::Mov, bpr, spr), + node!(None, Opcode::Pop, bpr), + node!(None, Opcode::Return), + ]); +} + +enum BfToken { + Inc, + Dec, + IncPtr, + DecPtr, + Out, + In, + Forward, + Back, +} + +fn lex(src: &str) -> Vec { + src.chars() + .filter_map(|c| match c { + '+' => Some(BfToken::Inc), + '-' => Some(BfToken::Dec), + '>' => Some(BfToken::IncPtr), + '<' => Some(BfToken::DecPtr), + '.' => Some(BfToken::Out), + ',' => Some(BfToken::In), + '[' => Some(BfToken::Forward), + ']' => Some(BfToken::Back), + _ => None, + }) + .collect() +} + +fn _create_symbol(id: u32) -> Symbol { + Symbol { + name: format!("label_{id}"), + module: Module::Resolved(0), + } +} diff --git a/assembler/src/tooling/mod.rs b/assembler/src/tooling/mod.rs new file mode 100644 index 0000000..294e630 --- /dev/null +++ b/assembler/src/tooling/mod.rs @@ -0,0 +1,2 @@ +pub mod brainf; +pub mod project; diff --git a/assembler/src/tooling/project.rs b/assembler/src/tooling/project.rs new file mode 100644 index 0000000..bdab388 --- /dev/null +++ b/assembler/src/tooling/project.rs @@ -0,0 +1,93 @@ +use crate::util::input; + +pub fn tool_libcreate() { + let mut ptype: String; + loop { + ptype = input("Enter project type (bin|lib)"); + if ptype == "bin" || ptype == "lib" { + break; + } + } + + let project_name = input("Enter project name"); + let project_path = input("Enter Directory to create project in"); + + println!("[ Creating new {ptype} project {project_name} in {project_path} ]"); + + let template = match ptype.as_str() { + "bin" => generate_bin_template(&project_name), + "lib" => generate_lib_template(&project_name), + _ => panic!("Invalid project type"), + }; + + let path = format!("{project_path}/{project_name}.dsa"); + std::fs::write(path, template).expect("Unable to write file"); +} + +fn generate_lib_template(module_name: &str) -> String { + format!( + r#"// {module_name}.dsa +// usage: +// +// include {module_name} "" +// +// usage for {module_name}_main: +// push (arg1) +// push (arg0) +// call {module_name}::{module_name}_main +// pop (arg0) +// pop (arg1) + +// Example data declarations +// dw example_data: 0x0000 + +// Main function template +{module_name}_main: + // the correct way to start a function as defined by the calling convention + push bpr + mov spr, bpr + + // explanation of how to access args + ldw bpr, rg0, 8 // arg 0 + ldw bpr, rg0, 12 // arg 1 + + // your code goes here + // Example: load example_data into rg1 + // ldw example_data, rg1 + + // the correct way to end a function as defined by the calling convention + mov bpr, spr + pop bpr + return +"#, + ) +} + +fn generate_bin_template(project_name: &str) -> String { + format!( + r#"// {project_name}.dsa +// Binary executable project + +// Example Dependencies +// include math "libs/math/math.dsa" +include print "../resources/dsa/print.dsa" + +// Data declarations - It is best practice to include these before any code! +dw message: "Hello from {project_name}.dsa!" // strings are automatically null terminated! + +// Program entry point - execution starts at the first non-definition line +{project_name}: + // Getting started: Calling external functions + // Syntax: push (arg1), push (arg0), call namespace::function, pop (arg0), pop (arg1) + + // Example: Print a string (if print library is included) + ldw message, rg0 // load address of message + push rg0 // push argument + call print::print // call the print function + pop rg0 // clean up stack + + // Program must end with halt instruction + halt +"#, + ) +} diff --git a/assembler/src/util/logging.rs b/assembler/src/util/logging.rs new file mode 100644 index 0000000..93c37e1 --- /dev/null +++ b/assembler/src/util/logging.rs @@ -0,0 +1,108 @@ +#![allow(dead_code)] +#![allow(unused)] +use std::{fmt, sync::mpsc::Sender}; + +pub struct Logger {} + +impl Logger { + pub const fn new() -> Self { + Self {} + } + + pub fn log(&self, message: &str) { + _ = self; + println!("\x1b[32mINFO:\x1b[0m {message}"); + } +} + +// #[derive(Debug)]= +// pub struct Logger { +// pub sender: Sender, +// } + +// impl Logger { +// pub fn new(sender: Sender) -> Self { +// Self { sender } +// } + +// pub fn debug(&self, message: T) { +// self.sender +// .send(Entry { +// etype: EntryType::Debug, +// message: message.to_string(), +// }) +// .unwrap(); +// } + +// pub fn info(&self, message: T) { +// self.sender +// .send(Entry { +// etype: EntryType::Info, +// message: message.to_string(), +// }) +// .unwrap(); +// } + +// pub fn warn(&self, message: T) { +// self.sender +// .send(Entry { +// etype: EntryType::Warn, +// message: message.to_string(), +// }) +// .unwrap(); +// } + +// pub fn error(&self, message: T) { +// self.sender +// .send(Entry { +// etype: EntryType::Error, +// message: message.to_string(), +// }) +// .unwrap(); +// } + +// pub fn fatal(&self, message: T) { +// self.sender +// .send(Entry { +// etype: EntryType::Fatal, +// message: message.to_string(), +// }) +// .unwrap(); +// } +// } + +pub struct Entry { + etype: EntryType, + pub message: String, +} + +#[derive(Copy, Clone, Eq, PartialEq)] +enum EntryType { + Debug, + Info, + Warn, + Error, + Fatal, +} + +impl fmt::Display for EntryType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{:<5}", + match self { + Self::Debug => "DEBUG", + Self::Info => "INFO", + Self::Warn => "WARN", + Self::Error => "ERROR", + Self::Fatal => "FATAL", + } + ) + } +} + +impl fmt::Display for Entry { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}: {}", self.etype, self.message) + } +} diff --git a/assembler/src/util/mod.rs b/assembler/src/util/mod.rs new file mode 100644 index 0000000..c8746e4 --- /dev/null +++ b/assembler/src/util/mod.rs @@ -0,0 +1,13 @@ +pub mod logging; + +use std::io::Write; + +pub fn input(prompt: &str) -> String { + print!("{prompt}\n > "); + std::io::stdout().flush().expect("Failed to flush stdout"); + let mut input = String::new(); + std::io::stdin() + .read_line(&mut input) + .expect("Failed to read line from stdin"); + input.trim().to_string() +} diff --git a/common/Cargo.toml b/common/Cargo.toml new file mode 100644 index 0000000..6d649a5 --- /dev/null +++ b/common/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "common" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[dependencies] diff --git a/common/src/instructions.rs b/common/src/instructions.rs new file mode 100644 index 0000000..032e4ae --- /dev/null +++ b/common/src/instructions.rs @@ -0,0 +1,517 @@ +use crate::{instructions::encode::Encode, prelude::*}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Interrupt { + Software(u8), + Breakpoint, + HardFault, +} + +pub type Address = u32; + +impl Interrupt { + // someone tell clippy to stfu. + #[allow(clippy::must_use_candidate)] + pub const fn as_u8(self) -> u8 { + match self { + Self::Breakpoint => 0, + Self::HardFault => 1, + Self::Software(code) => code, + } + } +} + +// TODO: This should be TryFrom. +impl From for Interrupt { + #[allow(unreachable_code)] + fn from(code: u8) -> Self { + match code { + 0 => Self::Breakpoint, + 1 => Self::HardFault, + _ => Self::Software(code), + } + } +} + +/// Whether an [`Instruction`] is an I-type or R-type instruction. +#[non_exhaustive] +pub enum InstructionType { + Register, + Immediate, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum Register { + // general purpose registers + Rg0, + Rg1, + Rg2, + Rg3, + Rg4, + Rg5, + Rg6, + Rg7, + Rg8, + Rg9, + Rga, + Rgb, + Rgc, + Rgd, + Rge, + Rgf, + + // special purpose registers + Acc, + Spr, + Bpr, + Ret, + Idr, + Mmr, + Zero, + NoReg, + + // system registers - can't be written to by instructions. + Mar, + Mdr, + Sts, + Cir, + Pcx, +} + +impl Register { + // this is here so clippy shuts up about the must_use tag. + #[allow(clippy::must_use_candidate)] + pub fn general() -> Vec { + vec![ + Self::Rg0, + Self::Rg1, + Self::Rg2, + Self::Rg3, + Self::Rg4, + Self::Rg5, + Self::Rg6, + Self::Rg7, + Self::Rg8, + Self::Rg9, + Self::Rga, + Self::Rgb, + Self::Rgc, + Self::Rgd, + Self::Rge, + Self::Rgf, + ] + } +} + +impl Default for Register { + fn default() -> Self { + Self::NoReg + } +} + +impl TryFrom for Register { + type Error = RegisterParseError; + + fn try_from(idx: u8) -> Result { + if idx > 0x1C { + return Err(RegisterParseError::InvalidIndex(idx)); + } + + Ok(match idx { + // System registers are not indexable in the reg file so they cannot be + // modified by instructions. + 0x0 => Self::Rg0, + 0x1 => Self::Rg1, + 0x2 => Self::Rg2, + 0x3 => Self::Rg3, + 0x4 => Self::Rg4, + 0x5 => Self::Rg5, + 0x6 => Self::Rg6, + 0x7 => Self::Rg7, + 0x8 => Self::Rg8, + 0x9 => Self::Rg9, + 0xA => Self::Rga, + 0xB => Self::Rgb, + 0xC => Self::Rgc, + 0xD => Self::Rgd, + 0xE => Self::Rge, + 0xF => Self::Rgf, + 0x10 => Self::Acc, + 0x11 => Self::Spr, + 0x12 => Self::Bpr, + 0x13 => Self::Ret, + 0x14 => Self::Idr, + 0x15 => Self::Mmr, + 0x16 => Self::Zero, + 0x17 => Self::NoReg, + 0x18 => Self::Mar, + 0x19 => Self::Mdr, + 0x1A => Self::Sts, + 0x1B => Self::Cir, + 0x1C => Self::Pcx, + _ => unreachable!("This is already checked for in top `if` branch."), + }) + } +} + +impl TryFrom<&str> for Register { + type Error = RegisterParseError; + + fn try_from(value: &str) -> Result { + match value.to_lowercase().as_str() { + "rg0" => Ok(Self::Rg0), + "rg1" => Ok(Self::Rg1), + "rg2" => Ok(Self::Rg2), + "rg3" => Ok(Self::Rg3), + "rg4" => Ok(Self::Rg4), + "rg5" => Ok(Self::Rg5), + "rg6" => Ok(Self::Rg6), + "rg7" => Ok(Self::Rg7), + "rg8" => Ok(Self::Rg8), + "rg9" => Ok(Self::Rg9), + "rga" => Ok(Self::Rga), + "rgb" => Ok(Self::Rgb), + "rgc" => Ok(Self::Rgc), + "rgd" => Ok(Self::Rgd), + "rge" => Ok(Self::Rge), + "rgf" => Ok(Self::Rgf), + "acc" => Ok(Self::Acc), + "spr" => Ok(Self::Spr), + "bpr" => Ok(Self::Bpr), + "ret" => Ok(Self::Ret), + "idr" => Ok(Self::Idr), + "mmr" => Ok(Self::Mmr), + "zero" => Ok(Self::Zero), + "null" => Ok(Self::NoReg), + "pcx" => Ok(Self::Pcx), + _ => Err(RegisterParseError::InvalidName(value.to_string())), + } + } +} + +impl std::fmt::Display for Register { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Rg0 => write!(f, "rg0"), + Self::Rg1 => write!(f, "rg1"), + Self::Rg2 => write!(f, "rg2"), + Self::Rg3 => write!(f, "rg3"), + Self::Rg4 => write!(f, "rg4"), + Self::Rg5 => write!(f, "rg5"), + Self::Rg6 => write!(f, "rg6"), + Self::Rg7 => write!(f, "rg7"), + Self::Rg8 => write!(f, "rg8"), + Self::Rg9 => write!(f, "rg9"), + Self::Rga => write!(f, "rga"), + Self::Rgb => write!(f, "rgb"), + Self::Rgc => write!(f, "rgc"), + Self::Rgd => write!(f, "rgd"), + Self::Rge => write!(f, "rge"), + Self::Rgf => write!(f, "rgf"), + Self::Acc => write!(f, "acc"), + Self::Spr => write!(f, "spr"), + Self::Bpr => write!(f, "bpr"), + Self::Ret => write!(f, "ret"), + Self::Idr => write!(f, "idr"), + Self::Mmr => write!(f, "mmr"), + Self::Zero => write!(f, "zero"), + Self::NoReg => write!(f, "noreg"), + Self::Mar => write!(f, "mar"), + Self::Mdr => write!(f, "mdr"), + Self::Sts => write!(f, "sts"), + Self::Cir => write!(f, "cir"), + Self::Pcx => write!(f, "pcx"), + } + } +} + +#[derive(Debug, Clone, Copy, Eq)] +#[repr(u8)] +#[non_exhaustive] +/// A list of all current instructions in the DSA. +/// +/// # Note +/// +/// This is subject to change and is therefore marked non exhaustive. +pub enum Instruction { + // No-op + Nop = 0x0, + + // Data transfer instructions + Mov(args::RTypeArgs) = 0x1, + MovSigned(args::RTypeArgs) = 0x2, + + LoadByte(args::ITypeArgs) = 0x3, + LoadByteSigned(args::ITypeArgs) = 0x4, + LoadHalfword(args::ITypeArgs) = 0x5, + LoadHalfwordSigned(args::ITypeArgs) = 0x6, + LoadWord(args::ITypeArgs) = 0x7, + + StoreByte(args::ITypeArgs) = 0x8, + StoreHalfword(args::ITypeArgs) = 0x9, + StoreWord(args::ITypeArgs) = 0xA, + + LoadLowerImmediate(args::ITypeArgs) = 0xB, + LoadUpperImmediate(args::ITypeArgs) = 0xC, + + // Jump Instructions + Jump(args::ITypeArgs) = 0xD, + JumpEq(args::ITypeArgs) = 0xE, + JumpNeq(args::ITypeArgs) = 0xF, + JumpGt(args::ITypeArgs) = 0x10, + JumpGe(args::ITypeArgs) = 0x11, + JumpLt(args::ITypeArgs) = 0x12, + JumpLe(args::ITypeArgs) = 0x13, + + // Comparison + Compare(args::RTypeArgs) = 0x14, + + // Arithmetic + Add(args::RTypeArgs) = 0x19, + Sub(args::RTypeArgs) = 0x1A, + Increment(args::RTypeArgs) = 0x15, + Decrement(args::RTypeArgs) = 0x16, + ShiftLeft(args::RTypeArgs) = 0x17, + ShiftRight(args::RTypeArgs) = 0x18, + + // Logical + And(args::RTypeArgs) = 0x1B, + Or(args::RTypeArgs) = 0x1C, + Not(args::RTypeArgs) = 0x1D, + Xor(args::RTypeArgs) = 0x1E, + Nand(args::RTypeArgs) = 0x1F, + Nor(args::RTypeArgs) = 0x20, + Xnor(args::RTypeArgs) = 0x21, + + // Misc + Interrupt(Interrupt) = 0x22, + IntReturn = 0x23, + Halt = 0x24, + + // Immediate Arithmetic + AddImmediate(args::ITypeArgs) = 0x25, + SubImmediate(args::ITypeArgs) = 0x26, + + // Fake Instructions + Data(u32) = 0x3E, + Segment(u32) = 0x3F, +} + +impl PartialEq for Instruction { + fn eq(&self, other: &Self) -> bool { + self.encode() == other.encode() + } +} + +impl Instruction { + /// Returns the opcode of an instruction. + /// + /// # Notes + /// + /// The top two bits shall be 0, opcodes are 6-bits long. + #[must_use] + pub const fn opcode(&self) -> u8 { + unsafe { *std::ptr::from_ref::(self).cast::() } + } + + /// Encodes an [`Instruction`] into a word. + #[must_use] + pub fn encode(&self) -> u32 { + Encode::encode(*self, self.opcode()) + } + + /// Decodes an [`Instruction`] from a word `data`. + pub fn decode(data: u32) -> Result { + data.try_into() + } + + /// Returns the mnemonic for a given [`Instruction`]. + #[must_use] + pub const fn mnemonic(self) -> &'static str { + match self { + Self::Add(_) => "add", + Self::Sub(_) => "sub", + Self::Increment(_) => "inc", + Self::Decrement(_) => "dec", + Self::Compare(_) => "cmp", + Self::Halt => "hlt", + Self::And(_) => "and", + Self::IntReturn => "intr", + Self::Interrupt(_) => "int", + Self::Jump(_) => "jmp", + Self::JumpEq(_) => "jeq", + Self::JumpNeq(_) => "jneq", + Self::JumpGt(_) => "jgt", + Self::JumpGe(_) => "jge", + Self::JumpLt(_) => "jlt", + Self::JumpLe(_) => "jle", + Self::Mov(_) => "mov", + Self::MovSigned(_) => "movs", + Self::LoadByte(_) => "ldb", + Self::LoadByteSigned(_) => "ldbs", + Self::LoadHalfword(_) => "ldh", + Self::LoadHalfwordSigned(_) => "ldhs", + Self::LoadWord(_) => "ldw", + Self::StoreByte(_) => "stb", + Self::StoreHalfword(_) => "sth", + Self::StoreWord(_) => "stw", + Self::LoadLowerImmediate(_) => "lli", + Self::LoadUpperImmediate(_) => "lui", + Self::ShiftLeft(_) => "shl", + Self::ShiftRight(_) => "shr", + Self::Or(_) => "or", + Self::Not(_) => "not", + Self::Nop => "nop", + Self::Xor(_) => "xor", + Self::Nand(_) => "nand", + Self::Nor(_) => "nor", + Self::Xnor(_) => "xnor", + Self::Data(_) => "data", + Self::AddImmediate(_) => "addi", + Self::SubImmediate(_) => "subi", + Self::Segment(_) => "[SEGMENT]", + } + } + + /// Returns the [`InstructionType`] for the given [`Instruction`]. + #[must_use] + pub const fn instruction_type(self) -> InstructionType { + Self::instruction_type_from_opcode(self.opcode()) + } + + /// Returns the [`InstructionType`] for the given `opcode`. + #[must_use] + pub const fn instruction_type_from_opcode(opcode: u8) -> InstructionType { + match opcode { + 0x3..=0x13 => InstructionType::Immediate, + _ => InstructionType::Register, + } + } +} + +// Instruction decoding logic goes here. +impl std::fmt::Display for Instruction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.mnemonic())?; + + match self { + Self::Mov(args) | Self::MovSigned(args) => { + write!(f, " {}, {}", args.sr1, args.dr) + } + Self::LoadByte(args) + | Self::LoadByteSigned(args) + | Self::LoadHalfword(args) + | Self::LoadHalfwordSigned(args) + | Self::LoadWord(args) + | Self::StoreByte(args) + | Self::StoreHalfword(args) + | Self::StoreWord(args) => { + write!( + f, + " {}({:x}/{}), {}", + args.r1, args.immediate, args.immediate, args.r2 + ) + } + Self::Jump(args) + | Self::JumpEq(args) + | Self::JumpNeq(args) + | Self::JumpGt(args) + | Self::JumpGe(args) + | Self::JumpLt(args) + | Self::JumpLe(args) => { + write!(f, " 0x{:x}/{}({})", args.immediate, args.immediate, args.r1) + } + Self::LoadLowerImmediate(args) | Self::LoadUpperImmediate(args) => { + write!(f, " 0x{:x}, {}, {}", args.immediate, args.r1, args.r2) + } + Self::Compare(args) | Self::Not(args) => { + write!(f, " {}, {}", args.sr1, args.sr2) + } + + Self::Add(args) + | Self::Sub(args) + | Self::Xor(args) + | Self::Nand(args) + | Self::Nor(args) + | Self::Xnor(args) + | Self::ShiftLeft(args) + | Self::ShiftRight(args) + | Self::And(args) + | Self::Or(args) => { + write!(f, " {}, {}, {}", args.sr1, args.sr2, args.dr) + } + + Self::AddImmediate(args) | Self::SubImmediate(args) => { + write!(f, " {}, {}, {}", args.r1, args.immediate, args.r2) + } + + Self::Increment(a) | Self::Decrement(a) => write!(f, " {}", a.sr1), + Self::Interrupt(a) => write!(f, " {}", a.as_u8()), + Self::Data(a) => write!(f, " {a}"), + Self::Segment(x) => write!(f, " [SEGMENT {x}]"), + _ => Ok(()), + } + } +} + +impl TryFrom for Instruction { + type Error = InstructionDecodeError; + + /// Instruction decoding can be using using [`Instruction::try_from`] + fn try_from(data: u32) -> Result { + // Pull the opcode out so we can parse it correctly. + let opcode = ((data >> 26) & 0x3F) as u8; + + match opcode { + 0x0 => Ok(Self::Nop), + 0x1 => Ok(Self::Mov(RTypeArgs::try_from(data)?)), + 0x2 => Ok(Self::MovSigned(RTypeArgs::try_from(data)?)), + 0x3 => Ok(Self::LoadByte(ITypeArgs::try_from(data)?)), + 0x4 => Ok(Self::LoadByteSigned(ITypeArgs::try_from(data)?)), + 0x5 => Ok(Self::LoadHalfword(ITypeArgs::try_from(data)?)), + 0x6 => Ok(Self::LoadHalfwordSigned(ITypeArgs::try_from(data)?)), + 0x7 => Ok(Self::LoadWord(ITypeArgs::try_from(data)?)), + 0x8 => Ok(Self::StoreByte(ITypeArgs::try_from(data)?)), + 0x9 => Ok(Self::StoreHalfword(ITypeArgs::try_from(data)?)), + 0xA => Ok(Self::StoreWord(ITypeArgs::try_from(data)?)), + 0xB => Ok(Self::LoadLowerImmediate(ITypeArgs::try_from(data)?)), + 0xC => Ok(Self::LoadUpperImmediate(ITypeArgs::try_from(data)?)), + 0xD => Ok(Self::Jump(ITypeArgs::try_from(data)?)), + 0xE => Ok(Self::JumpEq(ITypeArgs::try_from(data)?)), + 0xF => Ok(Self::JumpNeq(ITypeArgs::try_from(data)?)), + 0x10 => Ok(Self::JumpGt(ITypeArgs::try_from(data)?)), + 0x11 => Ok(Self::JumpGe(ITypeArgs::try_from(data)?)), + 0x12 => Ok(Self::JumpLt(ITypeArgs::try_from(data)?)), + 0x13 => Ok(Self::JumpLe(ITypeArgs::try_from(data)?)), + 0x14 => Ok(Self::Compare(RTypeArgs::try_from(data)?)), + 0x15 => Ok(Self::Increment(RTypeArgs::try_from(data)?)), + 0x16 => Ok(Self::Decrement(RTypeArgs::try_from(data)?)), + 0x17 => Ok(Self::ShiftLeft(RTypeArgs::try_from(data)?)), + 0x18 => Ok(Self::ShiftRight(RTypeArgs::try_from(data)?)), + 0x19 => Ok(Self::Add(RTypeArgs::try_from(data)?)), + 0x1A => Ok(Self::Sub(RTypeArgs::try_from(data)?)), + 0x1B => Ok(Self::And(RTypeArgs::try_from(data)?)), + 0x1C => Ok(Self::Or(RTypeArgs::try_from(data)?)), + 0x1D => Ok(Self::Not(RTypeArgs::try_from(data)?)), + 0x1E => Ok(Self::Xor(RTypeArgs::try_from(data)?)), + 0x1F => Ok(Self::Nand(RTypeArgs::try_from(data)?)), + 0x20 => Ok(Self::Nor(RTypeArgs::try_from(data)?)), + 0x21 => Ok(Self::Xnor(RTypeArgs::try_from(data)?)), + 0x22 => Ok(Self::Interrupt(Interrupt::from((data & 0xFF) as u8))), + 0x23 => Ok(Self::IntReturn), + 0x24 => Ok(Self::Halt), + 0x25 => Ok(Self::AddImmediate(ITypeArgs::try_from(data)?)), + 0x26 => Ok(Self::SubImmediate(ITypeArgs::try_from(data)?)), + 0x3F => Ok(Self::Segment(u32::from(data as u8))), + _ => Err(InstructionDecodeError::InvalidOpcode(opcode)), + } + } +} + +pub mod args; +mod encode; +pub mod errors; + +#[cfg(test)] +mod tests; diff --git a/common/src/instructions/args.rs b/common/src/instructions/args.rs new file mode 100644 index 0000000..664c31a --- /dev/null +++ b/common/src/instructions/args.rs @@ -0,0 +1,208 @@ +//! Various types of arguments that instructions can take, alongside encoding and decoding logic. + +use crate::{ + instructions::{RegisterParseError, encode::Encode}, + prelude::Register, +}; + +/// A list of errors that can be returned when decoding instruction arguments. +#[derive(Debug)] +pub enum ArgsDecodeError { + /// The register was not valid. + InvalidRegister(u8), +} + +impl From for ArgsDecodeError { + fn from(value: RegisterParseError) -> Self { + match value { + RegisterParseError::InvalidIndex(idx) => Self::InvalidRegister(idx), + RegisterParseError::InvalidName(_) => Self::InvalidRegister(0xFF), + } + } +} + +impl std::fmt::Display for ArgsDecodeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidRegister(idx) => { + write!(f, "invalid register index, got {idx:x}")?; + } + } + + Ok(()) + } +} + +impl std::error::Error for ArgsDecodeError {} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// Used by instructions with 2 registers and an immediate argument. +pub struct ITypeArgs { + pub immediate: u16, + pub r1: Register, + /// May not actually be used by some instructions taking an immediate e.g. LUI. This is solved by making the constructor take Options. + pub r2: Register, +} + +impl ITypeArgs { + #[must_use] + /// Creates a new [`ITypeArgs`]. If r1 or r2 is unset, they will be replaced with [`Register::NoReg`]. + pub fn new(immediate: u16, r1: Option, r2: Option) -> Self { + let r1 = r1.unwrap_or_default(); + let r2 = r2.unwrap_or_default(); + + Self { immediate, r1, r2 } + } +} + +impl Encode for ITypeArgs { + /// Encodes an I-type instruction from its fields. These must have some unused high-order + /// bits set to 0 else the bit shifting logic gets fucked. + fn encode(self, opcode: u8) -> u32 { + let opcode = u32::from(opcode); + let r1 = self.r1 as u32; + let dr = self.r2 as u32; + let immediate = u32::from(self.immediate); + + (opcode << 26) | (r1 << 21) | (dr << 16) | immediate + } +} + +impl TryFrom for ITypeArgs { + type Error = ArgsDecodeError; + + fn try_from(data: u32) -> Result { + let r1 = ((data >> 21) & 0x1F) as u8; + let r2 = ((data >> 16) & 0x1F) as u8; + let immediate = data as u16; + + let r1 = r1.try_into()?; + let r2 = r2.try_into()?; + + Ok(Self { immediate, r1, r2 }) + } +} + +/// Used by instructions not using immediates (besides 5 bit shift values). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct RTypeArgs { + pub sr1: Register, + pub sr2: Register, + pub dr: Register, + /// 5 bit shift amount. + pub shamt: u8, +} + +impl RTypeArgs { + #[must_use] + /// Creates a new [`RTypeArgs`]. If any registers are unset, they will be replaced with [`Register::NoReg`]. If `shamt` is unset, it will be set to 0. + pub fn new( + sr1: Option, + sr2: Option, + dr: Option, + shamt: Option, + ) -> Self { + let sr1 = sr1.unwrap_or_default(); + let shamt = shamt.unwrap_or_default(); + let sr2 = sr2.unwrap_or_default(); + let dr = dr.unwrap_or_default(); + + Self { + sr1, + sr2, + dr, + shamt, + } + } +} + +impl Encode for RTypeArgs { + /// Encodes an R-type instruction from its fields. These must have unused high-order + /// bits set to 0 else the bit shifting logic is fucked. + /// + /// # Arguments + /// + /// - `shamt`: The amount to shift value (used only in shift instructions, otherwise 0). + fn encode(self, opcode: u8) -> u32 { + let opcode = u32::from(opcode); + let sr1 = self.sr1 as u32; + let sr2 = self.sr2 as u32; + let dr = self.dr as u32; + let shamt = u32::from(self.shamt); + + (opcode << 26) | (sr1 << 21) | (sr2 << 16) | (dr << 11) | (shamt << 6) + } +} + +impl TryFrom for RTypeArgs { + type Error = ArgsDecodeError; + + fn try_from(data: u32) -> Result { + let sr1 = ((data >> 21) & 0x1F) as u8; + let sr2 = ((data >> 16) & 0x1F) as u8; + let dr = ((data >> 11) & 0x1F) as u8; + let shamt = ((data >> 6) & 0x1F) as u8; + + let sr1_reg = sr1.try_into()?; + let sr2_reg = sr2.try_into()?; + let dr_reg = dr.try_into()?; + + Ok(Self { + sr1: sr1_reg, + sr2: sr2_reg, + dr: dr_reg, + shamt, + }) + } +} + +#[macro_export] +macro_rules! args { + // R-type arguments - allows omitting any field + (R $(, $field:ident: $value:expr)* $(,)?) => {{ + let mut sr1: Option = None; + let mut sr2: Option = None; + let mut dr: Option = None; + let mut shamt: Option = None; + + $( + args!(@assign_r_option sr1, sr2, dr, shamt, $field, $value); + )* + + RTypeArgs::new(sr1, sr2, dr, shamt) + }}; + + // I-type arguments - requires immediate, allows omitting registers + (I, immediate: $immediate:expr $(, $field:ident: $value:expr)* $(,)?) => {{ + let mut r1: Option = None; + let mut r2: Option = None; + + $( + args!(@assign_i_option r1, r2, $field, $value); + )* + + ITypeArgs::new($immediate, r1, r2) + }}; + + // Internal helpers (same as above for R-type) + (@assign_r_option $sr1:ident, $sr2:ident, $dr:ident, $shamt:ident, sr1, $value:expr) => { + $sr1 = Some($value); + }; + (@assign_r_option $sr1:ident, $sr2:ident, $dr:ident, $shamt:ident, sr2, $value:expr) => { + $sr2 = Some($value); + }; + (@assign_r_option $sr1:ident, $sr2:ident, $dr:ident, $shamt:ident, dr, $value:expr) => { + $dr = Some($value); + }; + (@assign_r_option $sr1:ident, $sr2:ident, $dr:ident, $shamt:ident, shamt, $value:expr) => { + $shamt = Some($value); + }; + + // Internal helpers for I-type (without immediate handling) + (@assign_i_option $r1:ident, $r2:ident, r1, $value:expr) => { + $r1 = Some($value); + }; + (@assign_i_option $r1:ident, $r2:ident, r2, $value:expr) => { + $r2 = Some($value); + }; +} diff --git a/common/src/instructions/encode.rs b/common/src/instructions/encode.rs new file mode 100644 index 0000000..7b11a55 --- /dev/null +++ b/common/src/instructions/encode.rs @@ -0,0 +1,72 @@ +use crate::prelude::*; + +/// Not to be used directly, just call [`Instruction::encode`]. +pub trait Encode { + fn encode(self, opcode: u8) -> u32; +} + +/// Encodes a zero argument instruction. +fn encode_no_args(opcode: u8) -> u32 { + let opcode = u32::from(opcode); + let sr1 = Register::NoReg as u32; + let sr2 = Register::NoReg as u32; + let dr = Register::NoReg as u32; + let shamt = 0; + + (opcode << 26) | (sr1 << 21) | (sr2 << 16) | (dr << 11) | (shamt << 6) +} + +/// Expands to a match statement that calls encode on instructions that implement +/// [`Encode`]: +/// +/// # Usage +/// +/// ```rs +/// encode_instruction!(self, with_args: [...], no_args: [...], special: [...] ) +/// ``` +macro_rules! encode_instruction { + ($self:expr, with_args: [$($variant:ident),+ $(,)?], no_args: [$($no_arg_variant:ident),* $(,)?] $(, special: [$($special:pat => $body:expr),* $(,)?])?) => { + match $self { + $( + Instruction::$variant(args) => args.encode($self.opcode()), + )+ + $( + Instruction::$no_arg_variant => encode_no_args($self.opcode()), + )* + $($( + $special => $body, + )*)? + } + }; +} + +impl Encode for Instruction { + fn encode(self, _: u8) -> u32 { + encode_instruction!( + self, + with_args: [ + Mov, MovSigned, LoadByte, LoadByteSigned, LoadHalfword, + LoadHalfwordSigned, LoadWord, StoreByte, StoreHalfword, + StoreWord, LoadLowerImmediate, LoadUpperImmediate, Jump, + JumpEq, JumpNeq, JumpGt, JumpGe, JumpLt, JumpLe, Compare, + Add, Sub, Increment, Decrement, ShiftLeft, ShiftRight, + And, Or, Not, Xor, Nand, Nor, Xnor, AddImmediate, SubImmediate + ], + no_args: [Nop, IntReturn, Halt], + special: [ + Self::Data(data) => data, + Self::Interrupt(interrupt) => { + let opcode = u32::from(self.opcode()); + (opcode << 26) | u32::from(interrupt.as_u8()) + }, + Self::Segment(segment) => { + let opcode = u32::from(self.opcode()); + (opcode << 26) | u32::from(segment as u8) + } + ] + ) + } +} + +#[cfg(test)] +mod tests; diff --git a/common/src/instructions/encode/tests.rs b/common/src/instructions/encode/tests.rs new file mode 100644 index 0000000..e3bc63c --- /dev/null +++ b/common/src/instructions/encode/tests.rs @@ -0,0 +1,98 @@ +use crate::prelude::*; + +#[test] +fn test_encode_nop() { + let no_reg = Register::NoReg as u32; + let no_op = u32::from(Instruction::Nop.opcode()); + + let expected = (no_op << 26) | (no_reg << 21) | (no_reg << 16) | (no_reg << 11); + let got = Instruction::Nop.encode(); + + assert_eq!(expected, got); +} + +#[test] +fn test_encode_mov() { + let rg0 = Register::Rg0 as u32; + let rg1 = Register::Rg1 as u32; + let no_reg = Register::NoReg as u32; + + let instruction = Instruction::Mov(RTypeArgs::new( + Some(Register::Rg0), + None, + Some(Register::Rg1), + None, + )); + let mov = u32::from(instruction.opcode()); + + let expected = (mov << 26) | (rg0 << 21) | (no_reg << 16) | (rg1 << 11); + let got = instruction.encode(); + + assert_eq!(expected, got); +} + +#[test] +fn test_encode_load_byte() { + let rg0 = Register::Rg0 as u32; + let rg1 = Register::Rg1 as u32; + let immediate = 100; + + let instruction = Instruction::LoadByte(ITypeArgs::new( + immediate, + Some(Register::Rg0), + Some(Register::Rg1), + )); + let load_byte = u32::from(instruction.opcode()); + + let expected = (load_byte << 26) | (rg0 << 21) | (rg1 << 16) | u32::from(immediate); + let got = instruction.encode(); + + assert_eq!(expected, got); +} + +#[test] +fn test_encode_shift_left_shamt() { + let rg0 = Register::Rg0 as u32; + let no_reg = Register::NoReg as u32; + + let shift_amount = 5; + + let instruction = Instruction::ShiftLeft(RTypeArgs::new( + Some(Register::Rg0), + None, + None, + Some(shift_amount), + )); + let shift_left = u32::from(instruction.opcode()); + + let expected = (shift_left << 26) + | (rg0 << 21) + | (no_reg << 16) + | (no_reg << 11) + | (u32::from(shift_amount) << 6); + + let got = instruction.encode(); + + assert_eq!(expected, got); +} + +#[test] +fn test_encode_shift_left_reg() { + let rg0 = Register::Rg0 as u32; + let rg1 = Register::Rg1 as u32; + let no_reg = Register::NoReg as u32; + + let instruction = Instruction::ShiftLeft(RTypeArgs::new( + Some(Register::Rg0), + Some(Register::Rg1), + None, + None, + )); + let shift_left = u32::from(instruction.opcode()); + + let expected = (shift_left << 26) | (rg0 << 21) | (rg1 << 16) | (no_reg << 11); + + let got = instruction.encode(); + + assert_eq!(expected, got); +} diff --git a/common/src/instructions/errors.rs b/common/src/instructions/errors.rs new file mode 100644 index 0000000..8961ae5 --- /dev/null +++ b/common/src/instructions/errors.rs @@ -0,0 +1,56 @@ +//! All the errors that may be returned from [`instructions`]. + +use crate::prelude::*; + +#[derive(Debug)] +/// Error type for parsing register numbers. +pub enum RegisterParseError { + InvalidIndex(u8), + InvalidName(String), +} + +impl std::fmt::Display for RegisterParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidIndex(idx) => write!(f, "invalid index given ({idx})"), + Self::InvalidName(name) => write!(f, "invalid name given ({name})"), + } + } +} + +impl std::error::Error for RegisterParseError {} + +/// A list of errors that can be returned when decoding instructions. +#[derive(Debug)] +pub enum InstructionDecodeError { + /// Some field was incorrect. Returns an error for debugging purposes. + InvalidArgument(ArgsDecodeError), + /// Some opcode was invalid. Returns the offending opcode for debugging purposes etc. + InvalidOpcode(u8), +} + +impl From for InstructionDecodeError { + fn from(err: ArgsDecodeError) -> Self { + Self::InvalidArgument(err) + } +} + +impl std::fmt::Display for InstructionDecodeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidOpcode(code) => write!(f, "invalid opcode, got {code:x}")?, + Self::InvalidArgument(err) => write!(f, "invalid arguments, got an error {err}")?, + } + + Ok(()) + } +} + +impl std::error::Error for InstructionDecodeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::InvalidArgument(err) => Some(err), + _ => None, + } + } +} diff --git a/common/src/instructions/tests.rs b/common/src/instructions/tests.rs new file mode 100644 index 0000000..114e7da --- /dev/null +++ b/common/src/instructions/tests.rs @@ -0,0 +1,215 @@ +#![allow(clippy::unwrap_used)] +use crate::prelude::*; + +#[test] +fn test_opcode_nop() { + let instr = Instruction::Nop; + assert_eq!(instr.opcode(), 0x0); +} + +#[test] +fn test_opcode_data_transfer() { + let args = RTypeArgs::new(None, None, None, None); + assert_eq!(Instruction::Mov(args).opcode(), 0x1); + assert_eq!(Instruction::MovSigned(args).opcode(), 0x2); + + let iargs = ITypeArgs::new(0, None, None); + assert_eq!(Instruction::LoadByte(iargs).opcode(), 0x3); + assert_eq!(Instruction::LoadByteSigned(iargs).opcode(), 0x4); + assert_eq!(Instruction::LoadHalfword(iargs).opcode(), 0x5); + assert_eq!(Instruction::LoadHalfwordSigned(iargs).opcode(), 0x6); + assert_eq!(Instruction::LoadWord(iargs).opcode(), 0x7); + assert_eq!(Instruction::StoreByte(iargs).opcode(), 0x8); + assert_eq!(Instruction::StoreHalfword(iargs).opcode(), 0x9); + assert_eq!(Instruction::StoreWord(iargs).opcode(), 0xA); + assert_eq!(Instruction::LoadLowerImmediate(iargs).opcode(), 0xB); + assert_eq!(Instruction::LoadUpperImmediate(iargs).opcode(), 0xC); +} + +#[test] +fn test_opcode_jump_instructions() { + let args = ITypeArgs::new(0, None, None); + assert_eq!(Instruction::Jump(args).opcode(), 0xD); + assert_eq!(Instruction::JumpEq(args).opcode(), 0xE); + assert_eq!(Instruction::JumpNeq(args).opcode(), 0xF); + assert_eq!(Instruction::JumpGt(args).opcode(), 0x10); + assert_eq!(Instruction::JumpGe(args).opcode(), 0x11); + assert_eq!(Instruction::JumpLt(args).opcode(), 0x12); + assert_eq!(Instruction::JumpLe(args).opcode(), 0x13); +} + +#[test] +fn test_opcode_arithmetic() { + let args = RTypeArgs::new(None, None, None, None); + assert_eq!(Instruction::Compare(args).opcode(), 0x14); + assert_eq!(Instruction::Increment(args).opcode(), 0x15); + assert_eq!(Instruction::Decrement(args).opcode(), 0x16); + assert_eq!(Instruction::ShiftLeft(args).opcode(), 0x17); + assert_eq!(Instruction::ShiftRight(args).opcode(), 0x18); + assert_eq!(Instruction::Add(args).opcode(), 0x19); + assert_eq!(Instruction::Sub(args).opcode(), 0x1A); +} + +#[test] +fn test_opcode_logical() { + let args = RTypeArgs::new(None, None, None, None); + assert_eq!(Instruction::And(args).opcode(), 0x1B); + assert_eq!(Instruction::Or(args).opcode(), 0x1C); + assert_eq!(Instruction::Not(args).opcode(), 0x1D); + assert_eq!(Instruction::Xor(args).opcode(), 0x1E); + assert_eq!(Instruction::Nand(args).opcode(), 0x1F); + assert_eq!(Instruction::Nor(args).opcode(), 0x20); + assert_eq!(Instruction::Xnor(args).opcode(), 0x21); +} + +#[test] +fn test_opcode_misc() { + let interrupt = Interrupt::Software(5); + assert_eq!(Instruction::Interrupt(interrupt).opcode(), 0x22); + assert_eq!(Instruction::IntReturn.opcode(), 0x23); + assert_eq!(Instruction::Halt.opcode(), 0x24); +} + +#[test] +fn test_opcode_with_different_args() { + let args1 = RTypeArgs::new( + Some(Register::Rg0), + Some(Register::Rg1), + Some(Register::Rg2), + Some(5), + ); + let args2 = RTypeArgs::new( + Some(Register::Acc), + Some(Register::Spr), + Some(Register::Bpr), + Some(31), + ); + + // Opcode should be the same regardless of arguments + assert_eq!( + Instruction::Add(args1).opcode(), + Instruction::Add(args2).opcode() + ); + assert_eq!( + Instruction::Sub(args1).opcode(), + Instruction::Sub(args2).opcode() + ); +} + +#[test] +fn test_opcode_boundary_values() { + // Test highest opcode value + assert_eq!(Instruction::Halt.opcode(), 0x24); + + // Test lowest opcode value + assert_eq!(Instruction::Nop.opcode(), 0x0); +} + +#[test] +fn test_instruction_decode_nop() { + let instr = Instruction::Nop; + let encoded = instr.encode(); + let decoded = Instruction::decode(encoded).unwrap(); + assert_eq!(instr, decoded); +} + +#[test] +fn test_instruction_decode_data_transfer() { + let args = RTypeArgs::new( + Some(Register::Rg0), + Some(Register::Rg1), + Some(Register::Rg2), + Some(5), + ); + let instr = Instruction::Mov(args); + let encoded = instr.encode(); + let decoded = Instruction::decode(encoded).unwrap(); + assert_eq!(instr, decoded); + + let iargs = ITypeArgs::new(100, Some(Register::Rg3), Some(Register::Rg4)); + let instr = Instruction::LoadWord(iargs); + let encoded = instr.encode(); + let decoded = Instruction::decode(encoded).unwrap(); + assert_eq!(instr, decoded); +} + +#[test] +fn test_instruction_decode_jump() { + let args = ITypeArgs::new(200, Some(Register::Acc), Some(Register::Spr)); + let instr = Instruction::Jump(args); + let encoded = instr.encode(); + let decoded = Instruction::decode(encoded).unwrap(); + assert_eq!(instr, decoded); + + let instr = Instruction::JumpEq(args); + let encoded = instr.encode(); + let decoded = Instruction::decode(encoded).unwrap(); + assert_eq!(instr, decoded); +} + +#[test] +fn test_instruction_decode_arithmetic() { + let args = RTypeArgs::new( + Some(Register::Bpr), + Some(Register::Rg7), + Some(Register::Rgf), + Some(31), + ); + let instr = Instruction::Add(args); + let encoded = instr.encode(); + let decoded = Instruction::decode(encoded).unwrap(); + assert_eq!(instr, decoded); + + let instr = Instruction::Compare(args); + let encoded = instr.encode(); + let decoded = Instruction::decode(encoded).unwrap(); + assert_eq!(instr, decoded); +} + +#[test] +fn test_instruction_decode_logical() { + let args = RTypeArgs::new( + Some(Register::Rg8), + Some(Register::Rg9), + Some(Register::Rga), + Some(15), + ); + let instr = Instruction::And(args); + let encoded = instr.encode(); + let decoded = Instruction::decode(encoded).unwrap(); + assert_eq!(instr, decoded); + + let instr = Instruction::Xor(args); + let encoded = instr.encode(); + let decoded = Instruction::decode(encoded).unwrap(); + assert_eq!(instr, decoded); +} + +#[test] +fn test_instruction_decode_misc() { + let instr = Instruction::Halt; + let encoded = instr.encode(); + let decoded = Instruction::decode(encoded).unwrap(); + assert_eq!(instr, decoded); +} + +#[test] +fn test_instruction_decode_invalid() { + // Test with invalid opcode. + let invalid_encoded = 0xF500_0000; + let decode = Instruction::decode(invalid_encoded); + + dbg!(&decode); + + assert!(decode.is_err()); +} + +// TODO: Get interrupts working. +// #[test] +// fn test_instruction_decode_interrupt() { +// let interrupt = Interrupt::Software(10); +// let instr = Instruction::Interrupt(interrupt); +// let encoded = instr.encode(); +// let decoded = Instruction::decode(encoded).unwrap(); +// assert_eq!(instr, decoded); +// } diff --git a/common/src/lib.rs b/common/src/lib.rs new file mode 100644 index 0000000..f1bc305 --- /dev/null +++ b/common/src/lib.rs @@ -0,0 +1,22 @@ +#![deny( + clippy::unwrap_used, + clippy::nursery, + clippy::perf, + clippy::pedantic, + clippy::complexity +)] +#![allow( + clippy::cast_possible_truncation, + clippy::missing_panics_doc, + clippy::missing_errors_doc, + clippy::match_wildcard_for_single_variants +)] + +pub mod instructions; + +pub mod prelude { + //! A collection of types you should definitely import when working with this crate. + pub use super::instructions::{ + Address, Instruction, InstructionType, Interrupt, Register, args::*, errors::*, + }; +} diff --git a/dsa_editor/Cargo.lock b/dsa_editor/Cargo.lock new file mode 100644 index 0000000..818de07 --- /dev/null +++ b/dsa_editor/Cargo.lock @@ -0,0 +1,3985 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ab_glyph" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + +[[package]] +name = "accesskit" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d3b8f9bae46a948369bc4a03e815d4ed6d616bd00de4051133a5019dc31c5a" + +[[package]] +name = "accesskit_atspi_common" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c5dd55e6e94949498698daf4d48fb5659e824d7abec0d394089656ceaf99d4f" +dependencies = [ + "accesskit", + "accesskit_consumer", + "atspi-common", + "serde", + "thiserror 1.0.69", + "zvariant", +] + +[[package]] +name = "accesskit_consumer" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47983a1084940ba9a39c077a8c63e55c619388be5476ac04c804cfbd1e63459" +dependencies = [ + "accesskit", + "hashbrown", + "immutable-chunkmap", +] + +[[package]] +name = "accesskit_macos" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7329821f3bd1101e03a7d2e03bd339e3ac0dc64c70b4c9f9ae1949e3ba8dece1" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "accesskit_unix" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcee751cc20d88678c33edaf9c07e8b693cd02819fe89053776f5313492273f5" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel", + "async-executor", + "async-task", + "atspi", + "futures-lite", + "futures-util", + "serde", + "zbus", +] + +[[package]] +name = "accesskit_windows" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24fcd5d23d70670992b823e735e859374d694a3d12bfd8dd32bd3bd8bedb5d81" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown", + "paste", + "static_assertions", + "windows", + "windows-core", +] + +[[package]] +name = "accesskit_winit" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6a48dad5530b6deb9fc7a52cc6c3bf72cdd9eb8157ac9d32d69f2427a5e879" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "raw-window-handle", + "winit", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.9.1", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[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 = "arboard" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1df21f715862ede32a0c525ce2ca4d52626bb0007f8c18b87a384503ac33e70" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.1", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", + "parking_lot", + "percent-encoding", + "windows-sys 0.59.0", + "x11rb", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.0.7", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.0.7", + "tracing", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-signal" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.0.7", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atspi" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be534b16650e35237bb1ed189ba2aab86ce65e88cc84c66f4935ba38575cecbf" +dependencies = [ + "atspi-common", + "atspi-connection", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1909ed2dc01d0a17505d89311d192518507e8a056a48148e3598fef5e7bb6ba7" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus-lockstep", + "zbus-lockstep-macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-connection" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "430c5960624a4baaa511c9c0fcc2218e3b58f5dbcc47e6190cafee344b873333" +dependencies = [ + "atspi-common", + "atspi-proxies", + "futures-lite", + "zbus", +] + +[[package]] +name = "atspi-proxies" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e6c5de3e524cf967569722446bcd458d5032348554d9a17d7d72b041ab7496" +dependencies = [ + "atspi-common", + "serde", + "zbus", + "zvariant", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "serde", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" + +[[package]] +name = "bytemuck" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.9.1", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "colorful" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb474a9c3219a8254ead020421ecf1b90427f29b55f6aae9a2471fa62c126ef" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "common_syntax" +version = "0.1.0" +dependencies = [ + "colorful", + "eframe", + "egui", + "serde", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "ecolor" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc4feb366740ded31a004a0e4452fbf84e80ef432ecf8314c485210229672fd1" +dependencies = [ + "bytemuck", + "emath", +] + +[[package]] +name = "eframe" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0dfe0859f3fb1bc6424c57d41e10e9093fe938f426b691e42272c2f336d915c" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "egui-wgpu", + "egui-winit", + "egui_glow", + "glow", + "glutin", + "glutin-winit", + "image", + "js-sys", + "log", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "parking_lot", + "percent-encoding", + "profiling", + "raw-window-handle", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "winapi", + "windows-sys 0.59.0", + "winit", +] + +[[package]] +name = "egui" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" +dependencies = [ + "accesskit", + "ahash", + "bitflags 2.9.1", + "emath", + "epaint", + "log", + "nohash-hasher", + "profiling", +] + +[[package]] +name = "egui-wgpu" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d319dfef570f699b6e9114e235e862a2ddcf75f0d1a061de9e1328d92146d820" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "epaint", + "log", + "profiling", + "thiserror 1.0.69", + "type-map", + "web-time", + "wgpu", + "winit", +] + +[[package]] +name = "egui-winit" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d9dfbb78fe4eb9c3a39ad528b90ee5915c252e77bbab9d4ebc576541ab67e13" +dependencies = [ + "accesskit_winit", + "ahash", + "arboard", + "bytemuck", + "egui", + "log", + "profiling", + "raw-window-handle", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_glow" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "910906e3f042ea6d2378ec12a6fd07698e14ddae68aed2d819ffe944a73aab9e" +dependencies = [ + "ahash", + "bytemuck", + "egui", + "glow", + "log", + "memoffset", + "profiling", + "wasm-bindgen", + "web-sys", + "winit", +] + +[[package]] +name = "emath" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "epaint" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fcc0f5a7c613afd2dee5e4b30c3e6acafb8ad6f0edb06068811f708a67c562" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "log", + "nohash-hasher", + "parking_lot", + "profiling", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7e7a64c02cf7a5b51e745a9e45f60660a286f151c238b9d397b3e923f5082f" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" +dependencies = [ + "bitflags 2.9.1", + "cfg_aliases", + "cgl", + "dispatch2", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "objc2 0.6.1", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "once_cell", + "raw-window-handle", + "wayland-sys", + "windows-sys 0.52.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" +dependencies = [ + "cfg_aliases", + "glutin", + "raw-window-handle", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" +dependencies = [ + "gl_generator", + "windows-sys 0.52.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.9.1", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.9.1", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", + "png", + "tiff", +] + +[[package]] +name = "immutable-chunkmap" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f97096f508d54f8f8ab8957862eee2ccd628847b6217af1a335e1c44dee578" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "libc" +version = "0.2.173" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.2", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.9.1", + "libc", + "redox_syscall 0.5.13", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" +dependencies = [ + "bitflags 2.9.1", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "naga" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.9.1", + "cfg_aliases", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "rustc-hash 1.1.0", + "spirv", + "strum", + "termcolor", + "thiserror 2.0.12", + "unicode-xid", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.9.1", + "jni-sys", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.9.1", + "block2", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation 0.2.2", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.9.1", + "block2", + "dispatch", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation 0.2.2", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "orbclient" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" +dependencies = [ + "libredox", +] + +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.13", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.0.7", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" + +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.9.1", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" +dependencies = [ + "libc", + "smithay-client-toolkit", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix 1.0.7", + "windows-sys 0.59.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash 2.1.1", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wayland-backend" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" +dependencies = [ + "cc", + "downcast-rs", + "rustix 0.38.44", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" +dependencies = [ + "bitflags 2.9.1", + "rustix 0.38.44", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.9.1", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" +dependencies = [ + "rustix 0.38.44", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" +dependencies = [ + "bitflags 2.9.1", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" +dependencies = [ + "bitflags 2.9.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" +dependencies = [ + "bitflags 2.9.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" +dependencies = [ + "proc-macro2", + "quick-xml 0.37.5", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf4f3c0ba838e82b4e5ccc4157003fb8c324ee24c058470ffb82820becbde98" +dependencies = [ + "core-foundation 0.10.1", + "jni", + "log", + "ndk-context", + "objc2 0.6.1", + "objc2-foundation 0.3.1", + "url", + "web-sys", +] + +[[package]] +name = "weezl" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" + +[[package]] +name = "wgpu" +version = "24.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0b3436f0729f6cdf2e6e9201f3d39dc95813fad61d826c1ed07918b4539353" +dependencies = [ + "arrayvec", + "bitflags 2.9.1", + "cfg_aliases", + "document-features", + "js-sys", + "log", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "24.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f0aa306497a238d169b9dc70659105b4a096859a34894544ca81719242e1499" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.9.1", + "cfg_aliases", + "document-features", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.12", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "24.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f112f464674ca69f3533248508ee30cb84c67cf06c25ff6800685f5e0294e259" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bitflags 2.9.1", + "bytemuck", + "cfg_aliases", + "core-graphics-types", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-descriptor", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "ordered-float", + "parking_lot", + "profiling", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.12", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "windows", +] + +[[package]] +name = "wgpu-types" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" +dependencies = [ + "bitflags 2.9.1", + "js-sys", + "log", + "web-sys", +] + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[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" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winit" +version = "0.30.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.9.1", + "block2", + "bytemuck", + "calloop", + "cfg_aliases", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 0.38.44", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "xcursor" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" + +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.9.1", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus-lockstep" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca2c5dceb099bddaade154055c926bb8ae507a18756ba1d8963fd7b51d8ed1d" +dependencies = [ + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "zbus-lockstep", + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zbus_xml" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f374552b954f6abb4bd6ce979e6c9b38fb9d0cd7cc68a7d796e70c9f3a233" +dependencies = [ + "quick-xml 0.30.0", + "serde", + "static_assertions", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/dsa_editor/Cargo.toml b/dsa_editor/Cargo.toml new file mode 100644 index 0000000..adfbc95 --- /dev/null +++ b/dsa_editor/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "dsa_editor" +version = "0.1.0" +edition = "2024" +description = "a fork of a code editor egui widget adapted to work with DSA syntax." + +[dependencies] +egui = { version = "0.31", optional = true } +serde = { version = "1", optional = true } + +[lib] +name = "dsa_editor" +path = "src/lib.rs" + +[features] +default = ["egui", "editor"] +egui = ["dep:egui"] +editor = [] +serde = ["dep:serde"] + +[dev-dependencies] +eframe = "0.31" +colorful = "0.3" diff --git a/dsa_editor/src/highlighting.rs b/dsa_editor/src/highlighting.rs new file mode 100644 index 0000000..26380f5 --- /dev/null +++ b/dsa_editor/src/highlighting.rs @@ -0,0 +1,253 @@ +#[cfg(feature = "editor")] +use super::Editor; + +use super::syntax::{QUOTES, SEPARATORS, Syntax, TokenType}; +use std::mem; + +#[derive(Default, Debug, PartialEq, PartialOrd, Eq, Ord)] +/// Lexer and Token +pub struct Token { + ty: TokenType, + buffer: String, +} + +impl Token { + pub fn new>(ty: TokenType, buffer: S) -> Self { + Token { + ty, + buffer: buffer.into(), + } + } + pub fn ty(&self) -> TokenType { + self.ty + } + pub fn buffer(&self) -> &str { + &self.buffer + } + + fn first(&mut self, c: char, syntax: &Syntax) -> Option { + self.buffer.push(c); + let mut token = None; + self.ty = match c { + c if c.is_whitespace() => { + self.ty = TokenType::Whitespace(c); + token = self.drain(self.ty); + TokenType::Whitespace(c) + } + c if syntax.is_keyword(c.to_string().as_str()) => TokenType::Keyword, + c if syntax.is_type(c.to_string().as_str()) => TokenType::Type, + c if syntax.is_special(c.to_string().as_str()) => TokenType::Special, + c if syntax.comment == c.to_string().as_str() => TokenType::Comment(false), + c if syntax.comment_multiline[0] == c.to_string().as_str() => { + TokenType::Comment(true) + } + _ => TokenType::from(c), + }; + token + } + + fn drain(&mut self, ty: TokenType) -> Option { + let mut token = None; + if !self.buffer().is_empty() { + token = Some(Token { + buffer: mem::take(&mut self.buffer), + ty: self.ty, + }); + } + self.ty = ty; + token + } + + fn push_drain(&mut self, c: char, ty: TokenType) -> Option { + self.buffer.push(c); + self.drain(ty) + } + + fn drain_push(&mut self, c: char, ty: TokenType) -> Option { + let token = self.drain(self.ty); + self.buffer.push(c); + self.ty = ty; + token + } + + #[cfg(feature = "egui")] + /// Syntax highlighting + pub fn highlight(&mut self, editor: &T, text: &str) -> LayoutJob { + *self = Token::default(); + let mut job = LayoutJob::default(); + for c in text.chars() { + for token in self.automata(c, editor.syntax()) { + editor.append(&mut job, &token); + } + } + editor.append(&mut job, self); + job + } + + /// Lexer + pub fn tokens(&mut self, syntax: &Syntax, text: &str) -> Vec { + let mut tokens: Vec = text + .chars() + .flat_map(|c| self.automata(c, syntax)) + .collect(); + + if !self.buffer.is_empty() { + tokens.push(mem::take(self)); + } + tokens + } + + fn automata(&mut self, c: char, syntax: &Syntax) -> Vec { + use TokenType as Ty; + let mut tokens = vec![]; + match (self.ty, Ty::from(c)) { + (Ty::Comment(false), Ty::Whitespace('\n')) => { + self.buffer.push(c); + let n = self.buffer.pop(); + tokens.extend(self.drain(Ty::Whitespace(c))); + if let Some(n) = n { + tokens.extend(self.push_drain(n, self.ty)); + } + } + (Ty::Comment(false), _) => { + self.buffer.push(c); + } + (Ty::Comment(true), _) => { + self.buffer.push(c); + if self.buffer.ends_with(syntax.comment_multiline[1]) { + tokens.extend(self.drain(Ty::Unknown)); + } + } + (Ty::Literal | Ty::Punctuation(_), Ty::Whitespace(_)) => { + tokens.extend(self.drain(Ty::Whitespace(c))); + tokens.extend(self.first(c, syntax)); + } + (Ty::Hyperlink, Ty::Whitespace(_)) => { + tokens.extend(self.drain(Ty::Whitespace(c))); + tokens.extend(self.first(c, syntax)); + } + (Ty::Hyperlink, _) => { + self.buffer.push(c); + } + (Ty::Literal, _) => match c { + c if c == '(' => { + self.ty = Ty::Function; + tokens.extend(self.drain(Ty::Punctuation(c))); + tokens.extend(self.push_drain(c, Ty::Unknown)); + } + c if !c.is_alphanumeric() && !SEPARATORS.contains(&c) => { + tokens.extend(self.drain(self.ty)); + self.buffer.push(c); + self.ty = if QUOTES.contains(&c) { + Ty::Str(c) + } else { + Ty::Punctuation(c) + }; + } + _ => { + self.buffer.push(c); + self.ty = { + if self.buffer.starts_with(syntax.comment) { + Ty::Comment(false) + } else if self.buffer.starts_with(syntax.comment_multiline[0]) { + Ty::Comment(true) + } else if syntax.is_hyperlink(&self.buffer) { + Ty::Hyperlink + } else if syntax.is_keyword(&self.buffer) { + Ty::Keyword + } else if syntax.is_type(&self.buffer) { + Ty::Type + } else if syntax.is_special(&self.buffer) { + Ty::Special + } else { + Ty::Literal + } + }; + } + }, + (Ty::Numeric(false), Ty::Punctuation('.')) => { + self.buffer.push(c); + self.ty = Ty::Numeric(true); + } + (Ty::Numeric(_), Ty::Numeric(_)) => { + self.buffer.push(c); + } + (Ty::Numeric(_), Ty::Literal) => { + tokens.extend(self.drain(self.ty)); + self.buffer.push(c); + } + (Ty::Numeric(_), _) | (Ty::Punctuation(_), Ty::Literal | Ty::Numeric(_)) => { + tokens.extend(self.drain(self.ty)); + tokens.extend(self.first(c, syntax)); + } + (Ty::Punctuation(_), Ty::Str(_)) => { + tokens.extend(self.drain_push(c, Ty::Str(c))); + } + (Ty::Punctuation(_), _) => { + if !(syntax.comment.starts_with(&self.buffer) + || syntax.comment_multiline[0].starts_with(&self.buffer)) + { + tokens.extend(self.drain(self.ty)); + tokens.extend(self.first(c, syntax)); + } else { + self.buffer.push(c); + if self.buffer.starts_with(syntax.comment) { + self.ty = Ty::Comment(false); + } else if self.buffer.starts_with(syntax.comment_multiline[0]) { + self.ty = Ty::Comment(true); + } else if let Some(c) = self.buffer.pop() { + tokens.extend(self.drain(Ty::Punctuation(c))); + tokens.extend(self.first(c, syntax)); + } + } + } + (Ty::Str(q), _) => { + let control = self.buffer.ends_with('\\'); + self.buffer.push(c); + if c == q && !control { + tokens.extend(self.drain(Ty::Unknown)); + } + } + (Ty::Whitespace(_) | Ty::Unknown, _) => { + tokens.extend(self.first(c, syntax)); + } + // Keyword, Type, Special + (_reserved, Ty::Literal | Ty::Numeric(_)) => { + self.buffer.push(c); + self.ty = if syntax.is_keyword(&self.buffer) { + Ty::Keyword + } else if syntax.is_type(&self.buffer) { + Ty::Type + } else if syntax.is_special(&self.buffer) { + Ty::Special + } else { + Ty::Literal + }; + } + (reserved, _) => { + self.ty = reserved; + tokens.extend(self.drain(self.ty)); + tokens.extend(self.first(c, syntax)); + } + } + tokens + } +} + +#[cfg(feature = "egui")] +use egui::text::LayoutJob; + +#[cfg(feature = "egui")] +impl egui::util::cache::ComputerMut<(&T, &str), LayoutJob> for Token { + fn compute(&mut self, (cache, text): (&T, &str)) -> LayoutJob { + self.highlight(cache, text) + } +} + +#[cfg(feature = "egui")] +pub type HighlightCache = egui::util::cache::FrameCache; + +#[cfg(feature = "egui")] +pub fn highlight(ctx: &egui::Context, cache: &T, text: &str) -> LayoutJob { + ctx.memory_mut(|mem| mem.caches.cache::().get((cache, text))) +} diff --git a/dsa_editor/src/lib.rs b/dsa_editor/src/lib.rs new file mode 100644 index 0000000..e9dd0ad --- /dev/null +++ b/dsa_editor/src/lib.rs @@ -0,0 +1,296 @@ +pub mod highlighting; +mod syntax; +mod themes; + +#[cfg(feature = "egui")] +use egui::text::LayoutJob; +#[cfg(feature = "egui")] +use egui::widgets::text_edit::TextEditOutput; +pub use highlighting::Token; +#[cfg(feature = "egui")] +use highlighting::highlight; +#[cfg(feature = "editor")] +use std::hash::{Hash, Hasher}; +pub use syntax::{Syntax, TokenType}; +pub use themes::ColorTheme; +pub use themes::DEFAULT_THEMES; + +#[cfg(feature = "egui")] +pub trait Editor: Hash { + fn append(&self, job: &mut LayoutJob, token: &Token); + fn syntax(&self) -> &Syntax; +} + +#[cfg(feature = "editor")] +#[derive(Clone, Debug, PartialEq)] +/// CodeEditor struct which stores settings for highlighting. +pub struct CodeEditor { + id: String, + theme: ColorTheme, + syntax: Syntax, + numlines: bool, + numlines_shift: isize, + numlines_only_natural: bool, + fontsize: f32, + rows: usize, + stick_to_bottom: bool, + desired_width: f32, +} + +#[cfg(feature = "editor")] +impl Hash for CodeEditor { + fn hash(&self, state: &mut H) { + self.theme.hash(state); + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + (self.fontsize as u32).hash(state); + self.syntax.hash(state); + } +} + +#[cfg(feature = "editor")] +impl Default for CodeEditor { + fn default() -> CodeEditor { + CodeEditor { + id: String::from("Code Editor"), + theme: ColorTheme::THEME, + syntax: Syntax::dsa(), + numlines: true, + numlines_shift: 0, + numlines_only_natural: false, + fontsize: 10.0, + rows: 10, + stick_to_bottom: false, + desired_width: f32::INFINITY, + } + } +} + +#[cfg(feature = "editor")] +impl CodeEditor { + pub fn id_source(self, id_source: impl Into) -> Self { + CodeEditor { + id: id_source.into(), + ..self + } + } + + /// Minimum number of rows to show. + /// + /// **Default: 10** + pub fn with_rows(self, rows: usize) -> Self { + CodeEditor { rows, ..self } + } + + /// Use custom Color Theme + /// + /// **Default: Gruvbox** + pub fn with_theme(self, theme: ColorTheme) -> Self { + CodeEditor { theme, ..self } + } + + /// Use custom font size + /// + /// **Default: 10.0** + pub fn with_fontsize(self, fontsize: f32) -> Self { + CodeEditor { fontsize, ..self } + } + + #[cfg(feature = "egui")] + /// Use UI font size + pub fn with_ui_fontsize(self, ui: &mut egui::Ui) -> Self { + CodeEditor { + fontsize: egui::TextStyle::Monospace.resolve(ui.style()).size, + ..self + } + } + + /// Show or hide lines numbering + /// + /// **Default: true** + pub fn with_numlines(self, numlines: bool) -> Self { + CodeEditor { numlines, ..self } + } + + /// Shift lines numbering by this value + /// + /// **Default: 0** + pub fn with_numlines_shift(self, numlines_shift: isize) -> Self { + CodeEditor { + numlines_shift, + ..self + } + } + + /// Show lines numbering only above zero, useful for enabling numbering since nth row + /// + /// **Default: false** + pub fn with_numlines_only_natural(self, numlines_only_natural: bool) -> Self { + CodeEditor { + numlines_only_natural, + ..self + } + } + + /// Use custom syntax for highlighting + /// + /// **Default: Rust** + pub fn with_syntax(self, syntax: Syntax) -> Self { + CodeEditor { syntax, ..self } + } + + /// Should the containing area shrink if the content is small? + /// + /// **Default: false** + pub fn auto_shrink(self, shrink: bool) -> Self { + CodeEditor { + desired_width: if shrink { 0.0 } else { self.desired_width }, + ..self + } + } + + /// Sets the desired width of the code editor + /// + /// **Default: `f32::INFINITY`** + pub fn desired_width(self, width: f32) -> Self { + CodeEditor { + desired_width: width, + ..self + } + } + + /// Stick to bottom + /// The scroll handle will stick to the bottom position even while the content size + /// changes dynamically. This can be useful to simulate terminal UIs or log/info scrollers. + /// The scroll handle remains stuck until user manually changes position. Once "unstuck" + /// it will remain focused on whatever content viewport the user left it on. If the scroll + /// handle is dragged to the bottom it will again become stuck and remain there until manually + /// pulled from the end position. + /// + /// **Default: false** + pub fn stick_to_bottom(self, stick_to_bottom: bool) -> Self { + CodeEditor { + stick_to_bottom, + ..self + } + } + + #[cfg(feature = "egui")] + pub fn format(&self, ty: TokenType) -> egui::text::TextFormat { + let font_id = egui::FontId::monospace(self.fontsize); + let color = self.theme.type_color(ty); + egui::text::TextFormat::simple(font_id, color) + } + + #[cfg(feature = "egui")] + fn numlines_show(&self, ui: &mut egui::Ui, text: &str) { + let total = if text.ends_with('\n') || text.is_empty() { + text.lines().count() + 1 + } else { + text.lines().count() + } + .max(self.rows) as isize; + let max_indent = total.to_string().len().max( + !self.numlines_only_natural as usize * self.numlines_shift.to_string().len(), + ); + let mut counter = (1..=total) + .map(|i| { + let num = i + self.numlines_shift; + if num <= 0 && self.numlines_only_natural { + String::new() + } else { + let label = num.to_string(); + format!( + "{}{label}", + " ".repeat(max_indent.saturating_sub(label.len())) + ) + } + }) + .collect::>() + .join("\n"); + + #[allow(clippy::cast_precision_loss)] + let width = max_indent as f32 + * self.fontsize + * 0.5 + * !(total + self.numlines_shift <= 0 && self.numlines_only_natural) as u8 + as f32; + + let mut layouter = |ui: &egui::Ui, string: &str, _wrap_width: f32| { + let layout_job = egui::text::LayoutJob::single_section( + string.to_string(), + egui::TextFormat::simple( + egui::FontId::monospace(self.fontsize), + self.theme.type_color(TokenType::Comment(true)), + ), + ); + ui.fonts(|f| f.layout_job(layout_job)) + }; + + ui.add( + egui::TextEdit::multiline(&mut counter) + .id_source(format!("{}_numlines", self.id)) + .font(egui::TextStyle::Monospace) + .interactive(false) + .frame(false) + .desired_rows(self.rows) + .desired_width(width) + .layouter(&mut layouter), + ); + } + + #[cfg(feature = "egui")] + /// Show Code Editor + pub fn show( + &mut self, + ui: &mut egui::Ui, + text: &mut dyn egui::TextBuffer, + ) -> TextEditOutput { + let mut text_edit_output: Option = None; + let code_editor = |ui: &mut egui::Ui| { + ui.horizontal_top(|h| { + self.theme.modify_style(h, self.fontsize); + if self.numlines { + self.numlines_show(h, text.as_str()); + } + egui::ScrollArea::horizontal() + .hscroll(true) + .id_salt(format!("{}_inner_scroll", self.id)) + .show(h, |ui| { + let mut layouter = + |ui: &egui::Ui, string: &str, _wrap_width: f32| { + let layout_job = highlight(ui.ctx(), self, string); + ui.fonts(|f| f.layout_job(layout_job)) + }; + let output = egui::TextEdit::multiline(text) + .id_source(&self.id) + .lock_focus(true) + .desired_rows(self.rows) + .frame(false) + .desired_width(self.desired_width) + .layouter(&mut layouter) + .show(ui); + text_edit_output = Some(output); + }); + }); + }; + + egui::ScrollArea::vertical() + .id_salt(format!("{}_outer_scroll", self.id)) + .stick_to_bottom(self.stick_to_bottom) + .show(ui, code_editor); + + text_edit_output.expect("TextEditOutput should exist at this point") + } +} + +#[cfg(feature = "editor")] +#[cfg(feature = "egui")] +impl Editor for CodeEditor { + fn append(&self, job: &mut LayoutJob, token: &Token) { + job.append(token.buffer(), 0.0, self.format(token.ty())); + } + + fn syntax(&self) -> &Syntax { + &self.syntax + } +} diff --git a/dsa_editor/src/syntax/dsa.rs b/dsa_editor/src/syntax/dsa.rs new file mode 100644 index 0000000..4e6815e --- /dev/null +++ b/dsa_editor/src/syntax/dsa.rs @@ -0,0 +1,29 @@ +use super::Syntax; +use std::collections::BTreeSet; + +impl Syntax { + pub fn dsa() -> Self { + Syntax { + language: "Assembly", + case_sensitive: false, + comment: "//", + comment_multiline: ["/*", "*/"], + hyperlinks: BTreeSet::from(["http"]), + keywords: BTreeSet::from([ + "nop", "mov", "movs", "ldb", "ldbs", "ldh", "ldhs", "ldw", "stb", "sth", + "stw", "lli", "lui", "jmp", "jeq", "jne", "jgt", "jge", "jlt", "jle", + "cmp", "inc", "dec", "shl", "shr", "add", "sub", "and", "or", "not", + "xor", "nand", "nor", "xnor", "irt", "int", "hlt", + // pseduo-instructions + "db", "dh", "dw", "resb", "resh", "resw", "push", "pop", "lwi", "call", + "include", + ]), + types: BTreeSet::from([]), + special: BTreeSet::from([ + "rg0", "rg1", "rg2", "rg3", "rg4", "rg5", "rg6", "rg7", "rg8", "rg9", + "rga", "rgb", "rgc", "rgd", "rge", "rgf", "acc", "spr", "bpr", "ret", + "idr", "mmr", "zero", "null", "pcx", "mdr", "mar", "sts", "cir", + ]), + } + } +} diff --git a/dsa_editor/src/syntax/mod.rs b/dsa_editor/src/syntax/mod.rs new file mode 100644 index 0000000..12986f2 --- /dev/null +++ b/dsa_editor/src/syntax/mod.rs @@ -0,0 +1,204 @@ +#![allow(dead_code)] +pub mod dsa; + +use std::collections::BTreeSet; +use std::hash::{Hash, Hasher}; + +pub const SEPARATORS: [char; 1] = ['_']; +pub const QUOTES: [char; 3] = ['\'', '"', '`']; + +type MultiLine = bool; +type Float = bool; + +#[derive(Default, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum TokenType { + Comment(MultiLine), + Function, + Keyword, + Literal, + Hyperlink, + Numeric(Float), + Punctuation(char), + Special, + Str(char), + Type, + Whitespace(char), + #[default] + Unknown, +} +impl std::fmt::Debug for TokenType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut name = String::new(); + match &self { + TokenType::Comment(multiline) => { + name.push_str("Comment"); + { + if *multiline { + name.push_str(" MultiLine"); + } else { + name.push_str(" SingleLine"); + } + } + } + TokenType::Function => name.push_str("Function"), + TokenType::Keyword => name.push_str("Keyword"), + TokenType::Literal => name.push_str("Literal"), + TokenType::Hyperlink => name.push_str("Hyperlink"), + TokenType::Numeric(float) => { + name.push_str("Numeric"); + if *float { + name.push_str(" Float"); + } else { + name.push_str(" Integer"); + } + } + TokenType::Punctuation(_) => name.push_str("Punctuation"), + TokenType::Special => name.push_str("Special"), + TokenType::Str(quote) => { + name.push_str("Str "); + name.push(*quote); + } + TokenType::Type => name.push_str("Type"), + TokenType::Whitespace(c) => { + name.push_str("Whitespace"); + match c { + ' ' => name.push_str(" Space"), + '\t' => name.push_str(" Tab"), + '\n' => name.push_str(" New Line"), + _ => (), + }; + } + TokenType::Unknown => name.push_str("Unknown"), + }; + write!(f, "{name}") + } +} +impl From for TokenType { + fn from(c: char) -> Self { + match c { + c if c.is_whitespace() => TokenType::Whitespace(c), + c if QUOTES.contains(&c) => TokenType::Str(c), + c if c.is_numeric() => TokenType::Numeric(false), + c if c.is_alphabetic() || SEPARATORS.contains(&c) => TokenType::Literal, + c if c.is_ascii_punctuation() => TokenType::Punctuation(c), + _ => TokenType::Unknown, + } + } +} + +#[derive(Clone, Debug, PartialEq)] +/// Rules for highlighting. +pub struct Syntax { + pub language: &'static str, + pub case_sensitive: bool, + pub comment: &'static str, + pub comment_multiline: [&'static str; 2], + pub hyperlinks: BTreeSet<&'static str>, + pub keywords: BTreeSet<&'static str>, + pub types: BTreeSet<&'static str>, + pub special: BTreeSet<&'static str>, +} +impl Default for Syntax { + fn default() -> Self { + Syntax::dsa() + } +} +impl Hash for Syntax { + fn hash(&self, state: &mut H) { + self.language.hash(state); + } +} +impl Syntax { + pub fn new(language: &'static str) -> Self { + Syntax { + language, + ..Default::default() + } + } + pub fn with_case_sensitive(self, case_sensitive: bool) -> Self { + Syntax { + case_sensitive, + ..self + } + } + pub fn with_comment(self, comment: &'static str) -> Self { + Syntax { comment, ..self } + } + pub fn with_comment_multiline(self, comment_multiline: [&'static str; 2]) -> Self { + Syntax { + comment_multiline, + ..self + } + } + pub fn with_hyperlinks>>(self, hyperlinks: T) -> Self { + Syntax { + hyperlinks: hyperlinks.into(), + ..self + } + } + pub fn with_keywords>>(self, keywords: T) -> Self { + Syntax { + keywords: keywords.into(), + ..self + } + } + pub fn with_types>>(self, types: T) -> Self { + Syntax { + types: types.into(), + ..self + } + } + pub fn with_special>>(self, special: T) -> Self { + Syntax { + special: special.into(), + ..self + } + } + + pub fn language(&self) -> &str { + self.language + } + pub fn comment(&self) -> &str { + self.comment + } + pub fn is_hyperlink(&self, word: &str) -> bool { + self.hyperlinks.contains(word.to_ascii_lowercase().as_str()) + } + pub fn is_keyword(&self, word: &str) -> bool { + if self.case_sensitive { + self.keywords.contains(&word) + } else { + self.keywords.contains(word.to_ascii_lowercase().as_str()) + } + } + pub fn is_type(&self, word: &str) -> bool { + if self.case_sensitive { + self.types.contains(&word) + } else { + self.types.contains(word.to_ascii_lowercase().as_str()) + } + } + pub fn is_special(&self, word: &str) -> bool { + if self.case_sensitive { + self.special.contains(&word) + } else { + self.special.contains(word.to_ascii_lowercase().as_str()) + } + } +} + +impl Syntax { + pub fn simple(comment: &'static str) -> Self { + Syntax { + language: "", + case_sensitive: false, + comment, + comment_multiline: [comment; 2], + hyperlinks: BTreeSet::new(), + keywords: BTreeSet::new(), + types: BTreeSet::new(), + special: BTreeSet::new(), + } + } +} diff --git a/dsa_editor/src/themes/mod.rs b/dsa_editor/src/themes/mod.rs new file mode 100644 index 0000000..c126748 --- /dev/null +++ b/dsa_editor/src/themes/mod.rs @@ -0,0 +1,147 @@ +#![allow(dead_code)] +pub mod theme; + +use super::syntax::TokenType; +#[cfg(feature = "egui")] +use egui::Color32; + +#[cfg(feature = "egui")] +pub const ERROR_COLOR: Color32 = Color32::from_rgb(255, 0, 255); + +/// Array of default themes. +pub const DEFAULT_THEMES: [ColorTheme; 1] = [ColorTheme::THEME]; + +#[cfg(feature = "egui")] +fn color_from_hex(hex: &str) -> Option { + if hex == "none" { + return Some(Color32::from_rgba_premultiplied(255, 0, 255, 0)); + } + let rgb = (1..hex.len()) + .step_by(2) + .filter_map(|i| u8::from_str_radix(&hex[i..i + 2], 16).ok()) + .collect::>(); + let color = Color32::from_rgb(*rgb.first()?, *rgb.get(1)?, *rgb.get(2)?); + Some(color) +} + +#[derive(Hash, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +/// Colors in hexadecimal notation as used in HTML and CSS. +pub struct ColorTheme { + pub name: &'static str, + pub dark: bool, + pub bg: &'static str, + pub cursor: &'static str, + pub selection: &'static str, + pub comments: &'static str, + pub functions: &'static str, + pub keywords: &'static str, + pub literals: &'static str, + pub numerics: &'static str, + pub punctuation: &'static str, + pub strs: &'static str, + pub types: &'static str, + pub special: &'static str, +} + +impl Default for ColorTheme { + fn default() -> Self { + ColorTheme::THEME + } +} +impl ColorTheme { + pub fn name(&self) -> &str { + self.name + } + + pub fn is_dark(&self) -> bool { + self.dark + } + + #[cfg(feature = "egui")] + pub fn bg(&self) -> Color32 { + color_from_hex(self.bg).unwrap_or(ERROR_COLOR) + } + + #[cfg(feature = "egui")] + pub fn cursor(&self) -> Color32 { + color_from_hex(self.cursor).unwrap_or(ERROR_COLOR) + } + + #[cfg(feature = "egui")] + pub fn selection(&self) -> Color32 { + color_from_hex(self.selection).unwrap_or(ERROR_COLOR) + } + + #[cfg(feature = "egui")] + pub fn modify_style(&self, ui: &mut egui::Ui, fontsize: f32) { + let style = ui.style_mut(); + style.visuals.widgets.noninteractive.bg_fill = self.bg(); + style.visuals.window_fill = self.bg(); + style.visuals.selection.stroke.color = self.cursor(); + style.visuals.selection.bg_fill = self.selection(); + style.visuals.extreme_bg_color = self.bg(); + style.override_font_id = Some(egui::FontId::monospace(fontsize)); + style.visuals.text_cursor.stroke.width = fontsize * 0.1; + } + + pub const fn type_color_str(&self, ty: TokenType) -> &'static str { + match ty { + TokenType::Comment(_) => self.comments, + TokenType::Function => self.functions, + TokenType::Keyword => self.keywords, + TokenType::Literal => self.literals, + TokenType::Hyperlink => self.special, + TokenType::Numeric(_) => self.numerics, + TokenType::Punctuation(_) => self.punctuation, + TokenType::Special => self.special, + TokenType::Str(_) => self.strs, + TokenType::Type => self.types, + TokenType::Whitespace(_) | TokenType::Unknown => self.comments, + } + } + + #[cfg(feature = "egui")] + pub fn type_color(&self, ty: TokenType) -> Color32 { + match ty { + TokenType::Comment(_) => color_from_hex(self.comments), + TokenType::Function => color_from_hex(self.functions), + TokenType::Keyword => color_from_hex(self.keywords), + TokenType::Literal => color_from_hex(self.literals), + TokenType::Hyperlink => color_from_hex(self.special), + TokenType::Numeric(_) => color_from_hex(self.numerics), + TokenType::Punctuation(_) => color_from_hex(self.punctuation), + TokenType::Special => color_from_hex(self.special), + TokenType::Str(_) => color_from_hex(self.strs), + TokenType::Type => color_from_hex(self.types), + TokenType::Whitespace(_) | TokenType::Unknown => { + color_from_hex(self.comments) + } + } + .unwrap_or(ERROR_COLOR) + } + + pub fn monocolor( + dark: bool, + bg: &'static str, + fg: &'static str, + cursor: &'static str, + selection: &'static str, + ) -> Self { + ColorTheme { + name: "monocolor", + dark, + bg, + cursor, + selection, + literals: fg, + numerics: fg, + keywords: fg, + functions: fg, + punctuation: fg, + types: fg, + strs: fg, + comments: fg, + special: fg, + } + } +} diff --git a/dsa_editor/src/themes/theme.rs b/dsa_editor/src/themes/theme.rs new file mode 100644 index 0000000..757264d --- /dev/null +++ b/dsa_editor/src/themes/theme.rs @@ -0,0 +1,22 @@ +use super::ColorTheme; + +impl ColorTheme { + /// Author : Jakub Bartodziej + /// Theme uses the gruvbox dark palette with standard contrast + pub const THEME: ColorTheme = ColorTheme { + name: "Theme", + dark: true, + bg: "#1b1b1b", + cursor: "#de5252", // fg4 + selection: "#28323B", // bg2 + comments: "#444444", // gray1 + functions: "#7CCCC7", // green1 + keywords: "#6C81E0", // red1 + literals: "#A3ABFF", // fg1 + numerics: "#8A46CF", // purple1 + punctuation: "#99C9C9", // orange1 + strs: "#618c84", // aqua1 + types: "#B8B9D4", // yellow1 + special: "#de5252", // blue1 + }; +} diff --git a/emulator/Cargo.toml b/emulator/Cargo.toml new file mode 100644 index 0000000..23f7142 --- /dev/null +++ b/emulator/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "emulator" +version = "0.1.0" +edition = "2024" +default-run = "emulator" + +[lib] +name = "dsa_rs" +path = "src/lib.rs" +crate-type = ["cdylib", "rlib"] + +[[bin]] +name = "emulator" +required-features = ["config"] + +[dependencies] +common = { path = "../common" } +assembler = { path = "../assembler" } +dsa_editor = { path = "../dsa_editor" } +egui = "0.31.1" +dirs = "6.0.0" +discord-presence = { version = "1.6.0", optional = true } +toml = { version = "0.8.23", optional = true } +serde = { version = "1.0.219", features = ["derive"], optional = true } +egui_file = "0.22.1" + +[features] +default = ["config"] +discord-rpc = ["dep:discord-presence"] +config = ["dep:toml", "dep:serde"] + +# Add support for Android for the fun of it. Currently crashes lol. +[target.'cfg(target_os = "android")'.dependencies] +winit = { version = "0.30.11", features = ["android-native-activity"] } +# jni = "0.21.1" + +[target.'cfg(target_os = "android")'.dependencies.eframe] +version = "0.31.1" +features = ["android-native-activity"] + +[target.'cfg(not(target_os = "android"))'.dependencies.eframe] +version = "0.31.1" diff --git a/emulator/src/emulator/config.rs b/emulator/src/emulator/config.rs new file mode 100644 index 0000000..bf7d83a --- /dev/null +++ b/emulator/src/emulator/config.rs @@ -0,0 +1,38 @@ +//! Loads configuration information from a TOML file in the current working directory. +//! Currently doesn't do much but this may be expanded. + +use std::path::Path; + +use serde::Deserialize; + +#[derive(Deserialize, Default)] +pub struct Config { + pub misc: MiscTable, +} + +/// For config options where you aren't sure what table it should go under. +#[derive(Deserialize, Default)] +pub struct MiscTable { + /// Whether or not we can enable Discord RPC for fun. + #[cfg(feature = "discord-rpc")] + pub use_discord_rpc: bool, +} + +impl Config { + pub fn load(path: &Path) -> Result { + let file_contents = match std::fs::read_to_string(path) { + Ok(file_contents) => file_contents, + Err(why) => { + eprintln!( + "WARN: Expected to read config file from '{}' with error '{}'. Using default settings.", + path.display(), + why + ); + + return Ok(Self::default()); + } + }; + + Self::deserialize(toml::Deserializer::new(&file_contents)) + } +} diff --git a/emulator/src/emulator/misc/mod.rs b/emulator/src/emulator/misc/mod.rs new file mode 100644 index 0000000..06a3fd0 --- /dev/null +++ b/emulator/src/emulator/misc/mod.rs @@ -0,0 +1 @@ +pub mod rpc; diff --git a/emulator/src/emulator/misc/rpc.rs b/emulator/src/emulator/misc/rpc.rs new file mode 100644 index 0000000..3ef47e3 --- /dev/null +++ b/emulator/src/emulator/misc/rpc.rs @@ -0,0 +1,221 @@ +//! Just for fun I thought I would add a Discord RPC client to the emulator. +//! +//! This will display information like the current value of PCX, architecture name and +//! GitHub repo links to show off the ISA. Perhaps in the future if we cross-compile to +//! WASM we could include a link to run this software in the browser. +//! +//! +//! # Configuration +//! +//! This may be disabled like so in your `.dsa.emulator.toml` file: +//! +//! ```toml +//! [misc] +//! use_discord_rpc = false +//! ``` +//! +//! Alternatively, you can hide this in your Discord settings. + +#[cfg(feature = "discord-rpc")] +use std::{path::PathBuf, sync::Arc, time::Duration}; + +use std::sync::mpsc::{Receiver, Sender}; + +#[cfg(feature = "discord-rpc")] +use discord_presence::{Client, DiscordError, models::ActivityTimestamps}; + +use crate::emulator::config::Config; + +#[derive(Debug)] +#[cfg(feature = "discord-rpc")] +pub enum RpcClientError { + Client(DiscordError), +} + +#[cfg(feature = "discord-rpc")] +impl std::fmt::Display for RpcClientError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Client(why) => write!(f, "discord RPC error: {why}"), + } + } +} + +#[cfg(feature = "discord-rpc")] +impl std::error::Error for RpcClientError {} + +#[cfg(feature = "discord-rpc")] +impl From for RpcClientError { + fn from(err: DiscordError) -> Self { + Self::Client(err) + } +} + +/// The type of activity the user is currently doing. +#[derive(Debug, Clone)] +#[cfg(feature = "discord-rpc")] +pub enum Activity { + Idle, + EditingFile(PathBuf), +} + +/// Messages to send over the wire. +#[derive(Debug)] +#[cfg(feature = "discord-rpc")] +pub enum Message { + /// Sent when we want to update the [`Context`]. + Update(Activity), + /// Sent when the main program wants to exit. + Stop, +} + +#[cfg(feature = "discord-rpc")] +unsafe impl Send for Message {} + +#[derive(Debug, Clone)] +#[cfg(feature = "discord-rpc")] +pub struct RpcClient { + /// Sends updates to [`Context`] (our state). + sender: Sender, + /// Stored for later cleanup on Drop. + thread_handle: Option>>, +} + +#[cfg(feature = "discord-rpc")] +impl RpcClient { + #[expect(clippy::unreadable_literal)] + /// Sets up the [`RpcClient`]. + pub fn new( + sender: Sender, + reciever: Receiver, + ) -> Result { + // TODO: Put client id into a .env file. + let mut client = discord_presence::Client::new(1384303074088190042); + + let thread_handle = std::thread::spawn(move || { + client.start(); + + eprintln!("INFO: Started Discord RPC client."); + + std::thread::sleep(Duration::from_millis(1000)); + + // Recieve updates and do shit. + for message in &reciever { + match message { + Message::Update(activity) => { + Self::handle_activity(&mut client, &activity); + } + Message::Stop => { + eprintln!("INFO: Stopping discord RPC client."); + if let Err(why) = client.shutdown() { + eprintln!("ERROR: Stopping discord RPC client failed: {why}"); + } + + break; + } + } + } + }); + + Ok(Self { + sender, + thread_handle: Some(Arc::new(thread_handle)), + }) + } + + fn handle_activity(client: &mut Client, activity: &Activity) { + let current_time = std::time::SystemTime::now(); + let timestamps = ActivityTimestamps::new().start( + current_time + .duration_since(std::time::UNIX_EPOCH) + .expect("Failed to get UNIX timestamp for activity.") + .as_secs(), + ); + + match activity { + Activity::Idle => { + client + .set_activity(|act| act.details("Idle").timestamps(|_| timestamps)) + .expect("TODO: Exponential backoff."); + } + Activity::EditingFile(file_path) => { + client + .set_activity(|act| { + act.details(format!("Editing file: {}", file_path.display())) + .timestamps(|_| timestamps) + }) + .expect("TODO: Exponential backoff."); + } + } + + eprintln!("INFO: RPC sent: {activity:?}"); + } + + /// Stops the [`RpcClient`]. + /// + /// # Panics + /// + /// May panic if the reciever was deallocated. This should not happen. + fn stop(&self) { + self.sender + .send(Message::Stop) + .expect("Failed to send stop message to RPC client."); + } + + /// Send an update with a given [`Activity`] to the [`RpcClient`]. + /// + /// # Panics + /// + /// May panic if the reciever was deallocated. This should not happen. + pub fn update(&self, activity: Activity) { + self.sender + .send(Message::Update(activity)) + .expect("Failed to send update to RPC client. This should not happen."); + } +} + +// Possibly unneeded but good practice. +#[cfg(feature = "discord-rpc")] +impl Drop for RpcClient { + fn drop(&mut self) { + self.stop(); + + if let Some(handle) = self.thread_handle.take() { + if let Some(handle) = Arc::into_inner(handle) { + let _ = handle.join(); + } + } + } +} + +/// Stub for when the feature is disabled. +#[cfg(not(feature = "discord-rpc"))] +pub struct RpcClient {} + +/// Stub for when the feature is disabled. +#[cfg(not(feature = "discord-rpc"))] +pub enum Message {} + +/// Stub for when the feature is disabled. +#[cfg(not(feature = "discord-rpc"))] +pub enum Activity {} + +/// Gets the discord [`RpcClient`] or returns None if this has been disabled in the config +/// options. +#[cfg(feature = "config")] +#[allow(clippy::needless_pass_by_value, unused_variables)] +pub fn get_rpc_client_or_none( + config: &Config, + rpc_sender: Sender, + rpc_reciever: Receiver, +) -> Result, Box> { + #[cfg(not(feature = "discord-rpc"))] + return Ok(None); + + #[cfg(feature = "discord-rpc")] + if config.misc.use_discord_rpc { + Ok(Some(RpcClient::new(rpc_sender, rpc_reciever)?)) + } else { + Ok(None) + } +} diff --git a/emulator/src/emulator/mod.rs b/emulator/src/emulator/mod.rs new file mode 100644 index 0000000..9d6cc6f --- /dev/null +++ b/emulator/src/emulator/mod.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "config")] +pub mod config; +pub mod misc; +pub mod system; +pub mod ui; diff --git a/emulator/src/emulator/system/emulator.rs b/emulator/src/emulator/system/emulator.rs new file mode 100644 index 0000000..f6e5c0c --- /dev/null +++ b/emulator/src/emulator/system/emulator.rs @@ -0,0 +1,245 @@ +use std::sync::Arc; +use std::sync::mpsc::{self, Receiver, Sender}; + +#[allow(unused_imports)] +use crate::emulator::misc::rpc::{Activity, RpcClient}; + +use crate::emulator::system::model::StateUpdate; +use crate::emulator::system::{ + model::{Command, Running}, + processor::Processor, +}; + +use common::prelude::*; + +#[expect(clippy::too_many_lines)] +#[allow(unused_variables)] +pub fn run_emulator( + cmd_rx: &Receiver, + state_tx: &Sender, + mut processor: Processor, + rpc_client: Option<&Arc>, +) { + println!("INFO: Starting emulator."); + + let mut running = Running::Paused; + let mut step = 0; + let mut addr; + let mut history = Vec::<(u32, Instruction)>::new(); + let size = 256; + + state_tx + .send(StateUpdate::Running(Running::Paused)) + .expect("Failed to send initial state!"); + + let mut instruction_count = 0; + let mut update = false; + + loop { + let cmd = if running == Running::Running || step > 0 { + match cmd_rx.try_recv() { + Ok(cmd) => Some(cmd), + Err(mpsc::TryRecvError::Empty) => { + update = false; + None + } + Err(mpsc::TryRecvError::Disconnected) => break, + } + } else { + match cmd_rx.recv() { + Ok(cmd) => Some(cmd), + Err(_) => break, + } + }; + + if let Some(cmd) = cmd { + match cmd { + Command::Start => { + running = Running::Running; + + // Update RPC with current state. TODO: Make this only occur on state + // changes. + #[cfg(feature = "discord-rpc")] + if let Some(rpc_client) = rpc_client { + use std::{path::PathBuf, str::FromStr}; + + rpc_client.update(Activity::EditingFile( + PathBuf::from_str("test") + .expect("This is a valid path, WTF."), + )); + } + } + Command::Stop => { + running = Running::Paused; + } + Command::Reset(x) => { + running = Running::Paused; + + match x { + 0 => { + processor.clear(); + processor.reset(); + instruction_count = 0; + } + 1 => { + processor.reset(); + instruction_count = 0; + } + 2 => { + processor.clear(); + } + _ => unreachable!(), + } + + processor.reset(); + } + Command::Step(x) => { + step = x; + } + Command::Write(offset, data) => { + update = true; + + processor + .memory + .write_range(offset, data) + .unwrap_or_else(|_| { + report_err( + state_tx, + "Failed to write memory range!", + &mut processor, + ); + }); + } + Command::Interrupt(_interrupt) => { + update = true; + + todo!("implement interrupts") + } + Command::MemRequest(new, size) if update => { + addr = new; + let _ = state_tx.send(StateUpdate::MemoryView( + processor.memory.read_range(addr, size).unwrap_or_else(|_| { + report_err( + state_tx, + "Failed to read memory range!", + &mut processor, + ); + Vec::new() + }), + )); + } + Command::DisplayRequest if update => { + let _ = state_tx.send(StateUpdate::DisplayView( + processor.display().unwrap_or_else(|_| { + report_err( + state_tx, + "Failed to read display!", + &mut processor, + ); + Vec::new() + }), + )); + } + Command::StackRequest if update => { + let _ = state_tx.send(StateUpdate::StackView( + processor.get_stack(32).unwrap_or_else(|_| { + report_err(state_tx, "Failed to read stack!", &mut processor); + Vec::new() + }), + )); + } + Command::RegisterRequest if update => { + let _ = state_tx.send(StateUpdate::Registers(processor.registers)); + } + Command::RunningRequest if update => { + let _ = state_tx.send(StateUpdate::Running(running)); + } + Command::HistoryRequest if update => { + let hsc = history.clone(); + history.clear(); + let _ = state_tx.send(StateUpdate::InstructionHistory(hsc)); + } + Command::InstructionCountRequest if update => { + let _ = state_tx.send(StateUpdate::Instructions(instruction_count)); + } + Command::WriteBlock(addr, block) => { + processor + .memory + .write_range(addr, block.to_vec()) + .unwrap_or_else(|_| { + report_err( + state_tx, + "Failed to write memory block!", + &mut processor, + ); + }); + } + + _ => {} + } + } + + if step > 0 { + step -= 1; + update = true; + running = Running::Paused; + + // Execute one cycle. + match processor.cycle() { + Ok((addr, instruction)) => { + history.push((addr, instruction)); + } + Err(why) => { + let pcx = processor + .get(Register::Pcx) + .expect("SPR should never be invalid"); + report_err( + state_tx, + &format!( + "Could not decode instruction at {pcx:x}. Reason: {why}" + ), + &mut processor, + ); + } + } + instruction_count += 1; + continue; + } + + if running == Running::Running { + update = true; + + // Execute one cycle. + let instruction = match processor.cycle() { + Ok(instruction) => instruction, + Err(why) => { + let pcx = processor + .get(Register::Pcx) + .expect("PCX should never be invalid"); + report_err( + state_tx, + &format!( + "Could not decode instruction at {pcx:x}. Reason: {why}" + ), + &mut processor, + ); + (pcx, Instruction::Nop) + } + }; + + history.push(instruction); + if matches!(instruction.1, Instruction::Halt) { + running = Running::Halted; + } + + instruction_count += 1; + } + } +} + +fn report_err(state_tx: &Sender, why: &str, processor: &mut Processor) { + processor + .begin_interrupt(Interrupt::HardFault) + .expect("What kind of goofy ahh shenanigans did you do with your fault handler? At this point, the emulator can just crash. this is on you."); + let _ = state_tx.send(StateUpdate::Error(why.to_string())); +} diff --git a/emulator/src/emulator/system/memory.rs b/emulator/src/emulator/system/memory.rs new file mode 100644 index 0000000..b6a1425 --- /dev/null +++ b/emulator/src/emulator/system/memory.rs @@ -0,0 +1,170 @@ +use std::collections::HashMap; + +use crate::emulator::system::model::ProcessorError; + +pub trait MemoryUnit: Send + Sync { + fn reset(&mut self); + fn read_byte(&mut self, addr: u32) -> Result; + fn write_byte(&mut self, addr: u32, value: u8) -> Result<(), ProcessorError>; + fn read_word(&mut self, addr: u32) -> Result; + fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError>; + + fn read_range(&mut self, addr: u32, size: u32) -> Result, ProcessorError> { + let mut data = Vec::with_capacity(size as usize); + for i in 0..size { + data.push(self.read_byte(addr + i)?); + } + Ok(data) + } + + fn write_range(&mut self, addr: u32, value: Vec) -> Result<(), ProcessorError> { + for (i, byte) in value.into_iter().enumerate() { + self.write_byte(addr + i as u32, byte)?; + } + Ok(()) + } + + fn read_block(&mut self, addr: u32) -> Result<[u8; 256], ProcessorError> { + let mut data = [0; 256]; + for (i, byte) in data.iter_mut().enumerate() { + *byte = self.read_byte(addr + i as u32)?; + } + Ok(data) + } + + fn write_block(&mut self, addr: u32, data: [u8; 256]) -> Result<(), ProcessorError> { + for (i, byte) in data.iter().enumerate() { + self.write_byte(addr + i as u32, *byte)?; + } + Ok(()) + } +} + +pub struct MainStore { + pub data: HashMap, +} + +pub struct Block { + data: [u8; 256], +} + +impl Default for MainStore { + fn default() -> Self { + Self::new() + } +} + +impl MainStore { + #[must_use] + pub fn new() -> Self { + Self { + data: HashMap::new(), + } + } + + const fn segment_addr(addr: u32) -> (u32, u8) { + (addr / 256, (addr % 256) as u8) + } + + fn mut_block(&mut self, addr: u32) -> &mut Block { + self.data + .entry(addr) + .or_insert_with(|| Block { data: [0; 256] }); + + self.data.get_mut(&addr).map_or_else( + || panic!("Could not fetch block with address {addr:x?}"), + |block| block, + ) + } + + fn block(&mut self, addr: u32) -> &Block { + self.data + .entry(addr) + .or_insert_with(|| Block { data: [0; 256] }); + + self.data.get(&addr).map_or_else( + || panic!("Could not fetch block with address {addr:x?}"), + |block| block, + ) + } +} + +impl MemoryUnit for MainStore { + fn reset(&mut self) { + self.data.clear(); + } + + fn read_byte(&mut self, addr: u32) -> Result { + let (block_addr, offset) = Self::segment_addr(addr); + let block = self.block(block_addr); + Ok(block.data[offset as usize]) + } + + fn read_word(&mut self, addr: u32) -> Result { + if addr % 4 != 0 { + return Err(ProcessorError::BadMemoryAccess(addr)); + } + + let (block_addr, offset) = Self::segment_addr(addr); + let block = self.mut_block(block_addr); + let mut bytes = [0; 4]; + bytes[0] = block.data[offset as usize]; + bytes[1] = block.data[(offset + 1) as usize]; + bytes[2] = block.data[(offset + 2) as usize]; + bytes[3] = block.data[(offset + 3) as usize]; + Ok(u32::from_be_bytes(bytes)) + } + + fn read_range(&mut self, addr: u32, size: u32) -> Result, ProcessorError> { + let mut data = Vec::with_capacity(size as usize); + for i in 0..size { + data.push(self.read_byte(addr + i)?); + } + + Ok(data) + } + + fn write_byte(&mut self, addr: u32, value: u8) -> Result<(), ProcessorError> { + let (block_addr, offset) = Self::segment_addr(addr); + let block = self.mut_block(block_addr); + block.data[offset as usize] = value; + Ok(()) + } + + fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError> { + if addr % 4 != 0 { + return Err(ProcessorError::BadMemoryAccess(addr)); + } + + let (block_addr, offset) = Self::segment_addr(addr); + let block = self.mut_block(block_addr); + block.data[offset as usize] = (value >> 24) as u8; + block.data[(offset + 1) as usize] = (value >> 16) as u8; + block.data[(offset + 2) as usize] = (value >> 8) as u8; + block.data[(offset + 3) as usize] = value as u8; + Ok(()) + } + + fn write_range(&mut self, addr: u32, value: Vec) -> Result<(), ProcessorError> { + for (i, byte) in value.into_iter().enumerate() { + let (block_addr, offset) = Self::segment_addr(addr + i as u32); + let block = self.mut_block(block_addr); + block.data[offset as usize] = byte; + } + + Ok(()) + } + + fn read_block(&mut self, addr: u32) -> Result<[u8; 256], ProcessorError> { + let (block_addr, _) = Self::segment_addr(addr); + let block = self.block(block_addr); + Ok(block.data) + } + + fn write_block(&mut self, addr: u32, data: [u8; 256]) -> Result<(), ProcessorError> { + let (block_addr, _) = Self::segment_addr(addr); + let block = self.mut_block(block_addr); + block.data = data; + Ok(()) + } +} diff --git a/emulator/src/emulator/system/mod.rs b/emulator/src/emulator/system/mod.rs new file mode 100644 index 0000000..e483168 --- /dev/null +++ b/emulator/src/emulator/system/mod.rs @@ -0,0 +1,4 @@ +pub mod emulator; +pub mod memory; +pub mod model; +pub mod processor; diff --git a/emulator/src/emulator/system/model.rs b/emulator/src/emulator/system/model.rs new file mode 100644 index 0000000..2d49a94 --- /dev/null +++ b/emulator/src/emulator/system/model.rs @@ -0,0 +1,327 @@ +use std::sync::mpsc::{self, Receiver, Sender}; + +use common::prelude::*; + +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +pub enum Running { + Running, + Paused, + Halted, +} + +pub trait IODevice: Send + Sync { + fn read_byte(&mut self, addr: u32) -> u8; + fn write_byte(&mut self, addr: u32, value: u8); + fn read_range(&mut self, addr: u32, size: u32) -> Vec; + fn write_range(&mut self, addr: u32, value: Vec); +} + +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum Command { + // set emulator state. + Start, + Stop, + Step(usize), + Reset(usize), + Interrupt(Interrupt), + Write(Address, Vec), + WriteBlock(Address, Box<[u8; 256]>), + + // request emulator state. + MemRequest(Address, u32), + DisplayRequest, + StackRequest, + RegisterRequest, + RunningRequest, + HistoryRequest, + InstructionCountRequest, +} + +#[derive(Debug)] +pub enum ProcessorError { + InvalidInstruction(u32), + InvalidRegister(u8), + BadMemoryAccess(u32), +} + +impl std::error::Error for ProcessorError {} + +impl std::fmt::Display for ProcessorError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidInstruction(instruction) => { + write!(f, "Invalid instruction: {instruction}") + } + Self::InvalidRegister(register) => { + write!(f, "Invalid register: {register}") + } + Self::BadMemoryAccess(address) => { + write!(f, "Bad memory access: {address}") + } + } + } +} + +pub struct State { + pub state_receiver: Receiver, + pub cmd_sender: Sender, + + // Processor state + pub reg_file: RegFile, + pub running: Running, + pub instructions: usize, + + // Memory access views + pub stack_view: Vec, + pub memory_view: Vec, + pub display_view: Vec, + + pub error_log: Vec, + + pub instruction_history: Vec<(u32, Instruction)>, +} + +impl State { + #[must_use] + pub fn new(sender: Sender, receiver: Receiver) -> Self { + Self { + state_receiver: receiver, + cmd_sender: sender, + reg_file: RegFile::default(), + running: Running::Paused, + instructions: 0, + stack_view: vec![], + memory_view: vec![], + display_view: vec![], + error_log: vec![], + instruction_history: vec![], + } + } + + pub fn send(&mut self, cmd: Command) { + if let Err(e) = self.cmd_sender.send(cmd) { + self.error_log.push(e.to_string()); + } + } + + pub fn update(&mut self) -> Result<(), mpsc::TryRecvError> { + while let Ok(update) = self.state_receiver.try_recv() { + match update { + StateUpdate::Registers(reg_file) => self.reg_file = reg_file, + StateUpdate::Running(running) => self.running = running, + StateUpdate::Instructions(instructions) => { + self.instructions = instructions; + } + StateUpdate::StackView(stack_view) => self.stack_view = stack_view, + StateUpdate::MemoryView(memory_view) => self.memory_view = memory_view, + StateUpdate::DisplayView(display_view) => { + self.display_view = display_view; + } + StateUpdate::Error(err_state) => self.error_log.push(err_state), + StateUpdate::InstructionHistory(history) => { + self.instruction_history.extend(history); + } + } + + if self.error_log.len() > 256 { + self.error_log.drain(0..self.error_log.len() - 256); + } + + if self.instruction_history.len() > 1024 { + self.instruction_history + .drain(0..self.instruction_history.len() - 1024); + } + } + + if let Err(e) = self.state_receiver.try_recv() { + match e { + mpsc::TryRecvError::Empty => {} + mpsc::TryRecvError::Disconnected => { + return Err(e); + } + } + } + + Ok(()) + } +} + +pub enum StateUpdate { + Registers(RegFile), + Running(Running), + Instructions(usize), + StackView(Vec), + MemoryView(Vec), + DisplayView(Vec), + Error(String), + InstructionHistory(Vec<(u32, Instruction)>), +} + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +pub struct RegFile { + // General Purpose Registers + rg0: u32, + rg1: u32, + rg2: u32, + rg3: u32, + rg4: u32, + rg5: u32, + rg6: u32, + rg7: u32, + rg8: u32, + rg9: u32, + rga: u32, + rgb: u32, + rgc: u32, + rgd: u32, + rge: u32, + rgf: u32, + + // Special Purpose Registers + acc: u32, + spr: u32, + bpr: u32, + ret: u32, + idr: u32, + mmr: u32, + + // System Registers + mar: u32, + mdr: u32, + sts: u32, + cir: u32, + pcx: u32, +} + +impl RegFile { + #[must_use] + pub fn all(&self) -> Vec<(&str, u32)> { + vec![ + ("Rg0", self.rg0), + ("Rg1", self.rg1), + ("Rg2", self.rg2), + ("Rg3", self.rg3), + ("Rg4", self.rg4), + ("Rg5", self.rg5), + ("Rg6", self.rg6), + ("Rg7", self.rg7), + ("Rg8", self.rg8), + ("Rg9", self.rg9), + ("Rga", self.rga), + ("Rgb", self.rgb), + ("Rgc", self.rgc), + ("Rgd", self.rgd), + ("Rge", self.rge), + ("Rgf", self.rgf), + ("Acc", self.acc), + ("Spr", self.spr), + ("Bpr", self.bpr), + ("Ret", self.ret), + ("Idr", self.idr), + ("Mmr", self.mmr), + ("Mar", self.mar), + ("Mdr", self.mdr), + ("Sts", self.sts), + ("Cir", self.cir), + ("Pcx", self.pcx), + ] + } + + pub const fn reset(&mut self) { + self.rg0 = 0; + self.rg1 = 0; + self.rg2 = 0; + self.rg3 = 0; + self.rg4 = 0; + self.rg5 = 0; + self.rg6 = 0; + self.rg7 = 0; + self.rg8 = 0; + self.rg9 = 0; + self.rga = 0; + self.rgb = 0; + self.rgc = 0; + self.rgd = 0; + self.rge = 0; + self.rgf = 0; + self.acc = 0; + self.spr = 0; + self.bpr = 0; + self.ret = 0; + self.idr = 0; + self.mmr = 0; + self.mar = 0; + self.mdr = 0; + self.sts = 0; + self.cir = 0; + self.pcx = 0; + } + + pub const fn reg(&mut self, reg: Register) -> Result<&mut u32, ProcessorError> { + Ok(match reg { + Register::Rg0 => &mut self.rg0, + Register::Rg1 => &mut self.rg1, + Register::Rg2 => &mut self.rg2, + Register::Rg3 => &mut self.rg3, + Register::Rg4 => &mut self.rg4, + Register::Rg5 => &mut self.rg5, + Register::Rg6 => &mut self.rg6, + Register::Rg7 => &mut self.rg7, + Register::Rg8 => &mut self.rg8, + Register::Rg9 => &mut self.rg9, + Register::Rga => &mut self.rga, + Register::Rgb => &mut self.rgb, + Register::Rgc => &mut self.rgc, + Register::Rgd => &mut self.rgd, + Register::Rge => &mut self.rge, + Register::Rgf => &mut self.rgf, + Register::Acc => &mut self.acc, + Register::Spr => &mut self.spr, + Register::Bpr => &mut self.bpr, + Register::Ret => &mut self.ret, + Register::Idr => &mut self.idr, + Register::Mmr => &mut self.mmr, + Register::Mar => &mut self.mar, + Register::Mdr => &mut self.mdr, + Register::Sts => &mut self.sts, + Register::Cir => &mut self.cir, + Register::Pcx => &mut self.pcx, + _ => return Err(ProcessorError::InvalidRegister(Register::NoReg as u8)), + }) + } + + #[must_use] + pub const fn get(&self, reg: Register) -> Result { + Ok(match reg { + Register::Rg0 => self.rg0, + Register::Rg1 => self.rg1, + Register::Rg2 => self.rg2, + Register::Rg3 => self.rg3, + Register::Rg4 => self.rg4, + Register::Rg5 => self.rg5, + Register::Rg6 => self.rg6, + Register::Rg7 => self.rg7, + Register::Rg8 => self.rg8, + Register::Rg9 => self.rg9, + Register::Rga => self.rga, + Register::Rgb => self.rgb, + Register::Rgc => self.rgc, + Register::Rgd => self.rgd, + Register::Rge => self.rge, + Register::Rgf => self.rgf, + Register::Acc => self.acc, + Register::Spr => self.spr, + Register::Bpr => self.bpr, + Register::Ret => self.ret, + Register::Idr => self.idr, + Register::Mmr => self.mmr, + Register::Mar => self.mar, + Register::Mdr => self.mdr, + Register::Sts => self.sts, + Register::Cir => self.cir, + Register::Pcx => self.pcx, + Register::Zero => 0, + _ => return Err(ProcessorError::InvalidRegister(Register::NoReg as u8)), + }) + } +} diff --git a/emulator/src/emulator/system/processor/mod.rs b/emulator/src/emulator/system/processor/mod.rs new file mode 100644 index 0000000..d48b80d --- /dev/null +++ b/emulator/src/emulator/system/processor/mod.rs @@ -0,0 +1,507 @@ +use std::{ + cmp::{max, min}, + sync::Arc, +}; + +use crate::emulator::system::{ + memory::MemoryUnit, + model::{IODevice, ProcessorError, RegFile}, +}; + +use common::instructions::{Instruction, Interrupt, Register}; + +pub struct Processor { + pub memory: Box, + pub registers: RegFile, + pub halted: bool, + pub io_devices: Vec>, + + pub void: u32, +} + +fn log(message: &str) { + println!("\x1b[32mINFO:\x1b[0m {message}"); +} + +impl Processor { + #[must_use] + pub fn new(memory: Box, io_devices: Vec>) -> Self { + Self { + memory, + registers: RegFile::default(), + halted: false, + io_devices, + void: 0, + } + } + + pub const fn reset(&mut self) { + // set all registers to zero + // run memory.reset() + self.registers.reset(); + } + + pub fn clear(&mut self) { + self.memory.reset(); + } + + pub fn cycle(&mut self) -> Result<(u32, Instruction), ProcessorError> { + self.halted = false; + + // Get value from PCX. + let addr = self.fetch()?; + // Increment PCX. + self.advance(); + + // Set MAR to the previous value of PCX. + *self.reg(Register::Mar)? = addr; + let val = self.memory.read_word(addr)?; + + // Set CIR to the value of RAM[MAR]. + *self.reg(Register::Mar)? = val; + + // Decode and execute the instruction. + let instruction = Instruction::decode(val) + .map_err(|_| ProcessorError::InvalidInstruction(val))?; + + instruction.execute(self)?; + Ok((addr, instruction)) + } + + const fn fetch(&self) -> Result { + self.get(Register::Pcx) + } + + pub const fn get(&self, reg: Register) -> Result { + self.registers.get(reg) + } + + pub const fn reg(&mut self, reg: Register) -> Result<&mut u32, ProcessorError> { + match reg { + Register::Zero => Ok(&mut self.void), + _ => self.registers.reg(reg), + } + } + + pub fn display(&mut self) -> Result, ProcessorError> { + self.memory.read_range(0x20000, 2000) + } + + pub fn cmp(&mut self, a: u32, b: u32) { + self.set_flag(Flag::Equal, a == b); + self.set_flag(Flag::GreaterThan, a > b); + self.set_flag(Flag::LessThan, a < b); + } + + // functions to set new state + fn set_flag(&mut self, flag: Flag, value: bool) { + if value { + *self + .reg(Register::Sts) + .expect("STS should never be invalid") |= flag as u32; + } else { + *self + .reg(Register::Sts) + .expect("STS should never be invalid") &= !(flag as u32); + } + } + + fn get_flag(&self, flag: Flag) -> Result { + Ok(self.get(Register::Sts)? & (flag as u32) != 0) + } + + fn advance(&mut self) -> Result<(), ProcessorError> { + // increment PCX + *self.reg(Register::Pcx)? += 4; + Ok(()) + } + + fn jump(&mut self, reg: Register, offset: u16) -> Result<(), ProcessorError> { + *self.reg(Register::Pcx)? = self.get(reg)? + u32::from(offset); + Ok(()) + } + + pub fn begin_interrupt( + &mut self, + interrupt: Interrupt, + ) -> Result<(), ProcessorError> { + let idt = self.get(Register::Idr)?; + + let addr = self + .memory + .read_word(idt + u32::from(interrupt.as_u8()) * 4)?; + println!("INFO: Interrupt {interrupt:?} addr: {addr}"); + + self.push(self.get(Register::Pcx)?)?; + *self.reg(Register::Pcx)? = addr; + Ok(()) + } + + fn push(&mut self, val: u32) -> Result<(), ProcessorError> { + *self.reg(Register::Spr)? -= 4; + let reg = *self.reg(Register::Spr)?; + self.memory.write_word(reg, val) + } + + fn pop(&mut self) -> Result { + let reg = *self.reg(Register::Spr)?; + let val = self.memory.read_word(reg)?; + *self.reg(Register::Spr)? += 4; + Ok(val) + } + + // TODO: remove this once implemented + #[allow(clippy::needless_pass_by_ref_mut)] + fn end_interrupt(&mut self) -> Result<(), ProcessorError> { + let ret = self.pop()?; + *self.reg(Register::Ret)? = ret; + *self.reg(Register::Pcx)? = ret; + Ok(()) + } + + pub fn get_stack(&mut self, n: u32) -> Result, ProcessorError> { + let addr = self.get(Register::Spr)?; + let size = n * 4; + // returns the stack + self.memory.read_range( + max(addr, 0), // ensures that we cannot read from a negative address + min(size, addr), // ensures we don't read above the top of the stack + ) + } +} + +#[derive(Debug)] +#[expect(dead_code)] +enum Flag { + Equal = 1, + GreaterThan = 2, + LessThan = 4, + Zero = 8, + Positive = 16, + Negative = 32, + Carry = 64, + UserMode = 128, + InterruptsEnabled = 256, +} + +trait Executable { + fn execute(self, cpu: &mut Processor) -> Result<(), ProcessorError>; +} + +impl Executable for Instruction { + #[allow(clippy::too_many_lines)] + fn execute(self, cpu: &mut Processor) -> Result<(), ProcessorError> { + match self { + // No operation - a blank line. + // Copies from SrcReg to a.drReg. + Self::Mov(a) => { + *cpu.reg(a.dr)? = cpu.get(a.sr1)?; + } + + // Copies from SrcReg to a.drReg, sign extending the value to take up a full + // word. + Self::MovSigned(a) => { + *cpu.reg(a.dr)? = sign_extend(cpu.get(a.sr1)?); + } + + // Loads a byte from memory address (base + offset) into a.drReg. The + // effective address must be byte-aligned. + Self::LoadByte(a) => { + *cpu.reg(a.r2)? = u32::from( + cpu.memory + .read_byte(cpu.get(a.r1)? + u32::from(a.immediate))?, + ); + } + + // Loads a sign-extended byte from memory address (base + offset) into + // a.drReg. The effective address must be byte-aligned. + Self::LoadByteSigned(a) => { + *cpu.reg(a.r2)? = sign_extend(u32::from( + cpu.memory + .read_byte(cpu.get(a.r1)? + u32::from(a.immediate))?, + )); + } + + // Loads a half-word from memory address (base + offset) into a.drReg. The + // effective address must be 2-byte-aligned. + Self::LoadHalfword(a) => { + // we read an entire word, then right shift so we only get the first half + // of the word + *cpu.reg(a.r2)? = cpu + .memory + .read_word(cpu.get(a.r1)? + u32::from(a.immediate))? + >> 16; + } + + // Loads a sign-extended half-word from memory address (base + offset) into + // a.drReg. The effective address must be 2-byte-aligned. + Self::LoadHalfwordSigned(a) => { + *cpu.reg(a.r2)? = sign_extend( + cpu.memory + .read_word(cpu.get(a.r1)? + u32::from(a.immediate))? + >> 16, + ); + } + + // Loads a word from memory address (base + offset) into a.drReg. The + // effective address must be 4-byte-aligned. + Self::LoadWord(a) => { + *cpu.reg(a.r2)? = cpu + .memory + .read_word(cpu.get(a.r1)? + u32::from(a.immediate))?; + } + + // Stores a byte from SrcReg in memory address (base + offset) The effective + // address must be byte-aligned. + Self::StoreByte(a) => { + cpu.memory.write_byte( + cpu.get(a.r2)? + u32::from(a.immediate), + cpu.get(a.r1)? as u8, + )?; + } + + // Stores a half-word from SrcReg in memory address (base + offset) The + // effective address must be 2-byte-aligned. + Self::StoreHalfword(a) => { + // split the value into bytes and then write two bytes + let bytes = (cpu.get(a.r1)? as u16).to_le_bytes(); + cpu.memory + .write_byte(cpu.get(a.r2)? + u32::from(a.immediate), bytes[0])?; + cpu.memory + .write_byte(cpu.get(a.r2)? + u32::from(a.immediate) + 1, bytes[1])?; + } + + // Stores a word from SrcReg in memory address (base + offset) The effective + // address must be 4-byte-aligned. + Self::StoreWord(a) => { + cpu.memory.write_word( + cpu.get(a.r2)? + u32::from(a.immediate), + cpu.get(a.r1)?, + )?; + } + + // Loads a 16-bit literal value into reg, setting the bottom 16 bits of the + // word. To populate the upper 16 bits, see LUI. + Self::LoadLowerImmediate(a) => { + *cpu.reg(a.r1)? = u32::from(a.immediate); + } + + // Loads a 16-bit literal value into reg, setting the top 16 bits of the word. + // To populate the lower 16 bits, see LLI. + Self::LoadUpperImmediate(a) => { + *cpu.reg(a.r1)? = + (cpu.get(a.r1)? & 0x0000_FFFF) | (u32::from(a.immediate) << 16); + } + + // Unconditionally jumps to the calculated address or direct address + Self::Jump(a) => cpu.jump(a.r1, a.immediate)?, + + // Jumps to the calculated address or direct address if equal flag set. + Self::JumpEq(a) => { + if cpu.get_flag(Flag::Equal)? { + cpu.jump(a.r1, a.immediate)?; + } + } + + // Jumps to the calculated address or direct address if equal flag not set. + Self::JumpNeq(a) => { + if !cpu.get_flag(Flag::Equal)? { + cpu.jump(a.r1, a.immediate)?; + } + } + + // Jumps to the calculated address or direct address if greater than flag set. + Self::JumpGt(a) => { + if cpu.get_flag(Flag::GreaterThan)? { + cpu.jump(a.r1, a.immediate)?; + } + } + + // Jumps to the calculated address or direct address if greater than flag or + // equal flag set. + Self::JumpGe(a) => { + if cpu.get_flag(Flag::GreaterThan)? || cpu.get_flag(Flag::Equal)? { + cpu.jump(a.r1, a.immediate)?; + } + } + + // Jumps to the calculated address or direct address if less than flag set. + Self::JumpLt(a) => { + if cpu.get_flag(Flag::LessThan)? { + cpu.jump(a.r1, a.immediate)?; + } + } + + // Jumps to the calculated address or direct address if less than flag or + // equal flag set. + Self::JumpLe(a) => { + if cpu.get_flag(Flag::LessThan)? || cpu.get_flag(Flag::Equal)? { + cpu.jump(a.r1, a.immediate)?; + } + } + + // Increments the value in the given register + Self::Increment(a) => *cpu.reg(a.sr1)? = inc(cpu.get(a.sr1)?), + + // Decrements the value in the given register + Self::Decrement(a) => *cpu.reg(a.sr1)? = dec(cpu.get(a.sr1)?), + + // Left shifts the value in Reg by the given amount (either a register, or a + // literal value) + Self::ShiftLeft(a) => { + let reg = cpu.get(a.sr1)?; + let val = a.shamt; + *cpu.reg(a.sr1)? = shl(reg, val); + } + + // Right shifts the value in Reg by the given amount (either a register, or a + // literal value). + Self::ShiftRight(a) => { + let regval = cpu.get(a.sr1)?; + let val = a.shamt; + *cpu.reg(a.sr1)? = shr(regval, val); + } + + // Adds the value of Src2 to Src1 and writes the result to a.dr + Self::Add(a) => { + *cpu.reg(a.dr)? = add(cpu.get(a.sr1)?, cpu.get(a.sr2)?); + } + + // Subtracts the value of Src2 from Src1 and writes the result to a.dr + Self::Sub(a) => { + *cpu.reg(a.dr)? = sub(cpu.get(a.sr1)?, cpu.get(a.sr2)?); + } + + Self::AddImmediate(a) => { + *cpu.reg(a.r2)? = add(cpu.get(a.r1)?, u32::from(a.immediate)); + } + + Self::SubImmediate(a) => { + *cpu.reg(a.r2)? = sub(cpu.get(a.r1)?, u32::from(a.immediate)); + } + + // Performs bitwise AND on Src1 and Src2 storing the result in a.dr + Self::And(a) => *cpu.reg(a.dr)? = and(cpu.get(a.sr1)?, cpu.get(a.sr2)?), + + // Performs bitwise OR on Src1 and Src2 storing the result in a.dr + Self::Or(a) => *cpu.reg(a.dr)? = or(cpu.get(a.sr1)?, cpu.get(a.sr2)?), + + // Performs bitwise NOT on Src storing the result in a.dr + Self::Not(a) => *cpu.reg(a.dr)? = not(cpu.get(a.sr1)?), + + // Performs bitwise XOR on Src1 and Src2 storing the result in a.dr + Self::Xor(a) => *cpu.reg(a.dr)? = xor(cpu.get(a.sr1)?, cpu.get(a.sr2)?), + + // Performs bitwise NAND on Src1 and Src2 storing the result in a.dr + Self::Nand(a) => *cpu.reg(a.dr)? = nand(cpu.get(a.sr1)?, cpu.get(a.sr2)?), + + // Performs bitwise NOR on Src1 and Src2 storing the result in a.dr + Self::Nor(a) => *cpu.reg(a.dr)? = nor(cpu.get(a.sr1)?, cpu.get(a.sr2)?), + + // Performs bitwise XNOR on Src1 and Src2 storing the result in a.dr + Self::Xnor(a) => *cpu.reg(a.dr)? = xnor(cpu.get(a.sr1)?, cpu.get(a.sr2)?), + + // Compares the value of Reg1 to the value in Reg2. The results of the + // comparisons are set in the Status register. + Self::Compare(a) => { + cpu.cmp(cpu.get(a.sr1)?, cpu.get(a.sr2)?); + } + + // Initiates an interrupt with the given 8 bit interrupt code. + // Triggering an interrupt invokes the following behaviour: + // - The return address is saved to the RET register. + // - The stack base ptr is set to the kernel stack. + Self::Interrupt(interrupt_code) => { + cpu.begin_interrupt(interrupt_code)?; + } + + // Returns from an interrupt, + Self::IntReturn => { + cpu.end_interrupt()?; + } + + // Halts the processor. + Self::Halt => { + cpu.halted = true; + } + + Self::Segment(_) | Self::Nop | Self::Data(_) => {} + _ => { + eprintln!("WARN: unimplemented instruction: {self}"); + todo!() + } + } + Ok(()) + } +} + +// mathematical and logical functions & other operations +const fn add(a: u32, b: u32) -> u32 { + a.wrapping_add(b) +} + +const fn sub(a: u32, b: u32) -> u32 { + a.wrapping_sub(b) +} + +const fn and(a: u32, b: u32) -> u32 { + a & b +} + +const fn inc(a: u32) -> u32 { + a.wrapping_add(1) +} + +const fn dec(a: u32) -> u32 { + a.wrapping_sub(1) +} + +const fn shl(a: u32, amount: u8) -> u32 { + a << amount +} + +const fn shr(a: u32, amount: u8) -> u32 { + a >> amount +} + +const fn or(a: u32, b: u32) -> u32 { + a | b +} + +const fn not(a: u32) -> u32 { + !a +} + +const fn xor(a: u32, b: u32) -> u32 { + a ^ b +} + +const fn nand(a: u32, b: u32) -> u32 { + !(a & b) +} + +const fn nor(a: u32, b: u32) -> u32 { + !(a | b) +} + +const fn xnor(a: u32, b: u32) -> u32 { + !(a ^ b) +} + +const fn sign_extend(val: u32) -> u32 { + let (mask, sign_bit): (u32, u8) = match val { + 0..=0xFF => (0xFFFF_FF00, 7), + // I presume this was the intended behaviour? + 0x100..=0xFFFF => (0xFFFF_0000, 15), + _ => (0x0000_0000, 31), + }; + + if val & (1 << sign_bit) != 0 { + val | mask + } else { + val + } +} + +#[cfg(test)] +mod tests; diff --git a/emulator/src/emulator/system/processor/tests.rs b/emulator/src/emulator/system/processor/tests.rs new file mode 100644 index 0000000..6381e3a --- /dev/null +++ b/emulator/src/emulator/system/processor/tests.rs @@ -0,0 +1,695 @@ +use super::*; +use crate::emulator::system::memory::*; + +use common::prelude::*; + +fn create_test_processor() -> Processor { + let memory = Box::new(MainStore::new()); + Processor::new(memory, Vec::new()) +} + +#[test] +fn test_nop_instruction() { + let mut cpu = create_test_processor(); + let initial_state = cpu.registers; + + Instruction::Nop.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + + assert_eq!( + cpu.registers + .get(Register::Rg0) + .expect("Failed to get register Rg0"), + initial_state + .get(Register::Rg0) + .expect("Failed to get register Rg0") + ); + assert_eq!( + cpu.registers + .get(Register::Acc) + .expect("Failed to get register Acc"), + initial_state + .get(Register::Acc) + .expect("Failed to get register Acc") + ); +} + +#[test] +fn test_mov_instruction() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1234_5678; + + let mov_instr = Instruction::Mov(RTypeArgs::new( + Some(Register::Rg1), + None, + Some(Register::Rg2), + None, + )); + + mov_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg2).expect("Failed to get register Rg2"), + 0x1234_5678 + ); +} + +#[test] +fn test_mov_signed_instruction() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x0000_00FF; + + let mov_signed_instr = Instruction::MovSigned(RTypeArgs::new( + Some(Register::Rg1), + None, + Some(Register::Rg2), + None, + )); + + mov_signed_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg2).expect("Failed to get register Rg2"), + 0xFFFF_FFFF + ); +} + +#[test] +fn test_load_byte_instruction() { + let mut cpu = create_test_processor(); + let addr = 0x100; + cpu.memory + .write_byte(addr, 0xAB) + .expect("Failed to write byte to memory"); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr - 4; + + let load_byte_instr = Instruction::LoadByte(ITypeArgs::new( + 4, + Some(Register::Rg1), + Some(Register::Rg2), + )); + + load_byte_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg2).expect("Failed to get register Rg2"), + 0x0000_00AB + ); +} + +#[test] +fn test_load_byte_signed_instruction() { + let mut cpu = create_test_processor(); + let addr = 0x100; + cpu.memory + .write_byte(addr, 0xFF) + .expect("Failed to write byte to memory"); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr; + + let load_byte_signed_instr = Instruction::LoadByteSigned(ITypeArgs::new( + 0, + Some(Register::Rg1), + Some(Register::Rg2), + )); + + load_byte_signed_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg2).expect("Failed to get register Rg2"), + 0xFFFF_FFFF + ); +} + +#[test] +fn test_load_halfword_instruction() { + let mut cpu = create_test_processor(); + let addr = 0x100; + cpu.memory + .write_word(addr, 0x1234_5678) + .expect("Failed to write word to memory"); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr; + + let load_halfword_instr = Instruction::LoadHalfword(ITypeArgs::new( + 0, + Some(Register::Rg1), + Some(Register::Rg2), + )); + + load_halfword_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg2).expect("Failed to get register Rg2"), + 0x0000_1234 + ); +} + +#[test] +fn test_load_word_instruction() { + let mut cpu = create_test_processor(); + let addr = 0x100; + cpu.memory + .write_word(addr, 0x1234_5678) + .expect("Failed to write word to memory"); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr; + + let load_word_instr = Instruction::LoadWord(ITypeArgs::new( + 0, + Some(Register::Rg1), + Some(Register::Rg2), + )); + + load_word_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg2).expect("Failed to get register Rg2"), + 0x1234_5678 + ); +} + +#[test] +fn test_store_byte_instruction() { + let mut cpu = create_test_processor(); + let addr = 0x100; + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr; + *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0xAB; + + let store_byte_instr = Instruction::StoreByte(ITypeArgs::new( + 0, + Some(Register::Rg2), + Some(Register::Rg1), + )); + + store_byte_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!(cpu.memory.read_byte(addr).expect("Emulator was slain by losing the game while attempting to execute instruction"), 0xAB); +} + +#[test] +fn test_store_word_instruction() { + let mut cpu = create_test_processor(); + let addr = 0x100; + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr; + *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0x1234_5678; + + let store_word_instr = Instruction::StoreWord(ITypeArgs::new( + 0, + Some(Register::Rg2), + Some(Register::Rg1), + )); + + store_word_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!(cpu.memory.read_word(addr).expect("Emulator was slain by losing the game while attempting to execute instruction"), 0x1234_5678); +} + +#[test] +fn test_add_instruction() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 15; + *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 25; + + let add_instr = Instruction::Add(RTypeArgs::new( + Some(Register::Rg1), + Some(Register::Rg2), + Some(Register::Rg3), + None, + )); + + add_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg3).expect("Failed to get register Rg3"), + 40 + ); +} + +#[test] +fn test_sub_instruction() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 50; + *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 20; + + let sub_instr = Instruction::Sub(RTypeArgs::new( + Some(Register::Rg1), + Some(Register::Rg2), + Some(Register::Rg3), + None, + )); + + sub_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg3).expect("Failed to get register Rg3"), + 30 + ); +} + +#[test] +fn test_and_instruction() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100; + *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010; + + let and_instr = Instruction::And(RTypeArgs::new( + Some(Register::Rg1), + Some(Register::Rg2), + Some(Register::Rg3), + None, + )); + + and_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg3).expect("Failed to get register Rg3"), + 0b1000 + ); +} + +#[test] +fn test_or_instruction() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100; + *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010; + + let or_instr = Instruction::Or(RTypeArgs::new( + Some(Register::Rg1), + Some(Register::Rg2), + Some(Register::Rg3), + None, + )); + + or_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg3).expect("Failed to get register Rg3"), + 0b1110 + ); +} + +#[test] +fn test_xor_instruction() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100; + *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010; + + let xor_instr = Instruction::Xor(RTypeArgs::new( + Some(Register::Rg1), + Some(Register::Rg2), + Some(Register::Rg3), + None, + )); + + xor_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg3).expect("Failed to get register Rg3"), + 0b0110 + ); +} + +#[test] +fn test_not_instruction() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x0F0F_0F0F; + + let not_instr = Instruction::Not(RTypeArgs::new( + Some(Register::Rg1), + None, + Some(Register::Rg2), + None, + )); + + not_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg2).expect("Failed to get register Rg2"), + 0xF0F0_F0F0 + ); +} + +#[test] +fn test_compare_equal() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 42; + *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 42; + + let cmp_instr = Instruction::Compare(RTypeArgs::new( + Some(Register::Rg1), + Some(Register::Rg2), + None, + None, + )); + + cmp_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + + assert!(cpu.get_flag(Flag::Equal).expect("Failed to get flag Equal")); + assert!( + !cpu.get_flag(Flag::GreaterThan) + .expect("Failed to get flag GreaterThan") + ); + assert!( + !cpu.get_flag(Flag::LessThan) + .expect("Failed to get flag LessThan") + ); +} + +#[test] +fn test_compare_greater_than() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 50; + *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 30; + + let cmp_instr = Instruction::Compare(RTypeArgs::new( + Some(Register::Rg1), + Some(Register::Rg2), + None, + None, + )); + + cmp_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + + assert!(!cpu.get_flag(Flag::Equal).expect("Failed to get flag Equal")); + assert!( + cpu.get_flag(Flag::GreaterThan) + .expect("Failed to get flag GreaterThan") + ); + assert!( + !cpu.get_flag(Flag::LessThan) + .expect("Failed to get flag LessThan") + ); +} + +#[test] +fn test_compare_less_than() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 20; + *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 30; + + let cmp_instr = Instruction::Compare(RTypeArgs::new( + Some(Register::Rg1), + Some(Register::Rg2), + None, + None, + )); + + cmp_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + + assert!(!cpu.get_flag(Flag::Equal).expect("Failed to get flag Equal")); + assert!( + !cpu.get_flag(Flag::GreaterThan) + .expect("Failed to get flag GreaterThan") + ); + assert!( + cpu.get_flag(Flag::LessThan) + .expect("Failed to get flag LessThan") + ); +} + +#[test] +fn test_increment_instruction() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 42; + + let inc_instr = + Instruction::Increment(RTypeArgs::new(Some(Register::Rg1), None, None, None)); + + inc_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg1).expect("Failed to get register Rg1"), + 43 + ); +} + +#[test] +fn test_decrement_instruction() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 42; + + let dec_instr = + Instruction::Decrement(RTypeArgs::new(Some(Register::Rg1), None, None, None)); + + dec_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg1).expect("Failed to get register Rg1"), + 41 + ); +} + +#[test] +fn test_shift_left_with_shamt() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1010; + + let shl_instr = Instruction::ShiftLeft(RTypeArgs::new( + Some(Register::Rg1), + Some(Register::Zero), + None, + Some(2), + )); + + shl_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg1).expect("Failed to get register Rg1"), + 0b10_1000 + ); +} + +#[test] +fn test_shift_right_with_shamt() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b10_1000; + + let shr_instr = Instruction::ShiftRight(RTypeArgs::new( + Some(Register::Rg1), + Some(Register::Zero), + None, + Some(2), + )); + + shr_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg1).expect("Failed to get register Rg1"), + 0b1010 + ); +} + +// #[test] +// fn test_shift_left_with_register() { +// let mut cpu = create_test_processor(); +// *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1010; + +// let shl_instr = +// Instruction::ShiftLeft(RTypeArgs::new(Some(Register::Rg1), None, None, +// Some(3))); + +// shl_instr.execute(&mut cpu).expect( +// "Emulator was slain by losing the game while attempting to execute +// instruction", ); +// assert_eq!( +// cpu.get(Register::Rg1).expect("Failed to get register Rg1"), +// 0b101_0000 +// ); +// } + +#[test] +fn test_load_lower_immediate() { + let mut cpu = create_test_processor(); + + let lli_instr = Instruction::LoadLowerImmediate(ITypeArgs::new( + 0x1234, + Some(Register::Rg1), + None, + )); + + lli_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg1).expect("Failed to get register Rg1"), + 0x0000_1234 + ); +} + +#[test] +fn test_load_upper_immediate() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x0000_5678; + + let lui_instr = Instruction::LoadUpperImmediate(ITypeArgs::new( + 0x1234, + Some(Register::Rg1), + None, + )); + + lui_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg1).expect("Failed to get register Rg1"), + 0x1234_5678 + ); +} + +#[test] +fn test_jump_unconditional() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1000; + let initial_pc = cpu.get(Register::Pcx).expect("Failed to get register Pcx"); + + let jump_instr = Instruction::Jump(ITypeArgs::new(0x100, Some(Register::Rg1), None)); + + jump_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Pcx).expect("Failed to get register Pcx"), + 0x1100 + ); + assert_ne!( + cpu.get(Register::Pcx).expect("Failed to get register Pcx"), + initial_pc + ); +} + +#[test] +fn test_jump_equal_when_flag_set() { + let mut cpu = create_test_processor(); + cpu.set_flag(Flag::Equal, true); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1000; + + let jump_eq_instr = + Instruction::JumpEq(ITypeArgs::new(0x100, Some(Register::Rg1), None)); + + jump_eq_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Pcx).expect("Failed to get register Pcx"), + 0x1100 + ); +} + +#[test] +fn test_jump_equal_when_flag_not_set() { + let mut cpu = create_test_processor(); + cpu.set_flag(Flag::Equal, false); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1000; + let initial_pc = cpu.get(Register::Pcx).expect("Failed to get register Pcx"); + + let jump_eq_instr = + Instruction::JumpEq(ITypeArgs::new(0x100, Some(Register::Rg1), None)); + + jump_eq_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Pcx).expect("Failed to get register Pcx"), + initial_pc + ); +} + +#[test] +fn test_halt_instruction() { + let mut cpu = create_test_processor(); + assert!(!cpu.halted); + + Instruction::Halt.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert!(cpu.halted); +} + +#[test] +fn test_nand_instruction() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100; + *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010; + + let nand_instr = Instruction::Nand(RTypeArgs::new( + Some(Register::Rg1), + Some(Register::Rg2), + Some(Register::Rg3), + None, + )); + + nand_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg3).expect("Failed to get register Rg3"), + !0b1000 + ); +} + +#[test] +fn test_nor_instruction() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100; + *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010; + + let nor_instr = Instruction::Nor(RTypeArgs::new( + Some(Register::Rg1), + Some(Register::Rg2), + Some(Register::Rg3), + None, + )); + + nor_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg3).expect("Failed to get register Rg3"), + !0b1110 + ); +} + +#[test] +fn test_xnor_instruction() { + let mut cpu = create_test_processor(); + *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100; + *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010; + + let xnor_instr = Instruction::Xnor(RTypeArgs::new( + Some(Register::Rg1), + Some(Register::Rg2), + Some(Register::Rg3), + None, + )); + + xnor_instr.execute(&mut cpu).expect( + "Emulator was slain by losing the game while attempting to execute instruction", + ); + assert_eq!( + cpu.get(Register::Rg3).expect("Failed to get register Rg3"), + !0b0110 + ); +} diff --git a/emulator/src/emulator/ui/control_unit.rs b/emulator/src/emulator/ui/control_unit.rs new file mode 100644 index 0000000..2cee3bb --- /dev/null +++ b/emulator/src/emulator/ui/control_unit.rs @@ -0,0 +1,197 @@ +use crate::emulator::{ + system::model::{Command, Running, State}, + ui::interface::Component, +}; + +use common::{instructions::Register, prelude::Instruction}; + +pub struct ControlPanel { + visible: bool, + step_amount_input: String, + step_amount: usize, +} + +impl ControlPanel { + #[allow(clippy::must_use_candidate)] + pub fn new() -> Self { + Self { + visible: false, + step_amount_input: String::from("1"), + step_amount: 1, + } + } +} + +impl Default for ControlPanel { + fn default() -> Self { + Self::new() + } +} + +impl Component for ControlPanel { + fn category(&self) -> super::interface::Category { + super::interface::Category::Control + } + + fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + fn name(&self) -> &'static str { + "Control Panel" + } + + fn render(&mut self, state: &mut State, ui: &mut egui::Ui, ctx: &egui::Context) { + ui.horizontal(|ui| { + // Pause / Run + if ui + .button(if state.running == Running::Running { + "Pause" + } else { + "Run" + }) + .clicked() + { + if state.running == Running::Running { + state.cmd_sender.send(Command::Stop).unwrap_or_else(|_| { + state.error_log.push("Failed to send command".to_string()); + }); + } else { + state.cmd_sender.send(Command::Start).unwrap_or_else(|_| { + state.error_log.push("Failed to send command".to_string()); + }); + } + } + + // Step + if ui.button("Step").clicked() { + state + .cmd_sender + .send(Command::Step(self.step_amount)) + .unwrap_or_else(|_| { + state.error_log.push("Failed to send command".to_string()); + }); + } + + // Resets the emulator and all attached devices + if ui.button("Reset All").clicked() { + state + .cmd_sender + .send(Command::Reset(0)) + .unwrap_or_else(|_| { + state.error_log.push("Failed to send command".to_string()); + }); + } + + // Resets the emulator and all attached devices + if ui.button("Clear Registers").clicked() { + state + .cmd_sender + .send(Command::Reset(1)) + .unwrap_or_else(|_| { + state.error_log.push("Failed to send command".to_string()); + }); + } + + // Resets the emulator and all attached devices + if ui.button("Clear RAM").clicked() { + state + .cmd_sender + .send(Command::Reset(2)) + .unwrap_or_else(|_| { + state.error_log.push("Failed to send command".to_string()); + }); + } + + ui.separator(); + + state.send(Command::RegisterRequest); + state.send(Command::RunningRequest); + state.send(Command::InstructionCountRequest); + + if ui + .text_edit_singleline(&mut self.step_amount_input) + .changed() + { + self.step_amount = if let Ok(amount) = self.step_amount_input.parse() { + amount + } else { + state + .error_log + .push("Unable to parse step amount".to_string()); + 1 + } + } + + // Status info + ui.label(format!( + "Status: {}", + match state.running { + Running::Running => "Running", + Running::Paused => "Paused", + Running::Halted => "Halted", + } + )); + + let pcx = state + .reg_file + .get(Register::Pcx) + .expect("PCX should never be invalid"); + let instructions = state.instructions; + + ui.label(format!("Instructions: {instructions}")); + ui.label(format!("PC: 0x{pcx:08X}")); + + let instruction = Instruction::decode( + state + .reg_file + .get(Register::Cir) + .expect("CIR should never be invalid"), + ) + .map_or_else( + |_| "Invalid Instruction".to_string(), + |instruction| instruction.to_string(), + ); + + ui.label(format!("Instruction: {instruction}")); + }); + + render_register_table(state, ui, ctx); + } +} + +fn render_register_table(state: &State, ui: &mut egui::Ui, _ctx: &egui::Context) { + // Left column - Registers + ui.vertical(|ui| { + ui.heading("Registers"); + + egui::ScrollArea::vertical() + .id_salt("register_inspector_scroll") + .show(ui, |ui| { + egui::Grid::new("registers_grid") + .num_columns(8) + .spacing([40.0, 4.0]) + .striped(true) + .show(ui, |ui| { + ui.label("Register"); + ui.label("Value"); + ui.label("Register"); + ui.label("Value"); + ui.label("Register"); + ui.label("Value"); + ui.label("Register"); + ui.label("Value"); + ui.end_row(); + + // iterate over state.reg_file.iter() in chunks of 4 registers + for chunk in state.reg_file.all().chunks(4) { + for reg in chunk { + ui.label(reg.0.to_string()); + ui.label(format!("0x{:08X} ({})", reg.1, reg.1,)); + } + ui.end_row(); + } + }); + }) + }); +} diff --git a/emulator/src/emulator/ui/display.rs b/emulator/src/emulator/ui/display.rs new file mode 100644 index 0000000..90ee4a9 --- /dev/null +++ b/emulator/src/emulator/ui/display.rs @@ -0,0 +1,92 @@ +use crate::emulator::{ + system::model::{Command, State}, + ui::interface::{Category, Component}, +}; + +use eframe::egui; +use egui::{Color32, FontId, Vec2}; + +const VGA_WIDTH: usize = 80; +const VGA_HEIGHT: usize = 25; + +pub struct Display { + visible: bool, +} + +impl Display { + #[must_use] + pub const fn new() -> Self { + Self { visible: false } + } +} + +impl Default for Display { + fn default() -> Self { + Self::new() + } +} + +impl Component for Display { + fn name(&self) -> &'static str { + "Display" + } + + fn category(&self) -> Category { + Category::IO + } + + fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + fn render(&mut self, state: &mut State, ui: &mut egui::Ui, _ctx: &egui::Context) { + state.send(Command::DisplayRequest); + + let display: Vec = state.display_view.clone(); + let font_id = FontId::monospace(12.0); + + let char_width = ui.fonts(|f| f.glyph_width(&font_id, 'W')); + let line_height = ui.fonts(|f| f.row_height(&font_id)); + + #[expect(clippy::cast_precision_loss)] + let display_size = Vec2::new( + char_width * VGA_WIDTH as f32, + line_height * VGA_HEIGHT as f32, + ); + + let (rect, _response) = ui.allocate_exact_size(display_size, egui::Sense::all()); + + // Fill background + // ui.painter().rect_filled(rect, 0.0, Color32::BLACK); + + // Draw text + for y in 0..VGA_HEIGHT { + let mut row_text = String::with_capacity(VGA_WIDTH); + for x in 0..VGA_WIDTH { + let index = y * VGA_WIDTH + x; + if index < display.len() { + let byte = display[index]; + let ch = if (32..=126).contains(&byte) { + byte as char + } else { + ' ' + }; + row_text.push(ch); + } else { + row_text.push(' '); + } + } + + #[expect(clippy::cast_precision_loss)] + let text_pos = rect.min + Vec2::new(0.0, y as f32 * line_height); + + ui.painter().text( + text_pos, + egui::Align2::LEFT_TOP, + row_text, + font_id.clone(), + Color32::WHITE, + ); + } + } +} diff --git a/emulator/src/emulator/ui/editor.rs b/emulator/src/emulator/ui/editor.rs new file mode 100644 index 0000000..e49ad85 --- /dev/null +++ b/emulator/src/emulator/ui/editor.rs @@ -0,0 +1,545 @@ +use std::fmt::Write; +use std::{ + ffi::OsStr, + fs, + path::{Path, PathBuf}, +}; + +use common::prelude::Instruction; +use egui::{Align, Context, Key, Layout, Ui}; + +use dsa_editor::{CodeEditor, ColorTheme, Syntax}; +use egui_file::FileDialog; + +use crate::emulator::{ + system::model::{Command, State}, + ui::interface::Component, +}; + +use assembler::prelude::*; + +#[derive(Default)] +pub struct Editor { + // editor state + path: Option, + unsaved: bool, + text: String, + buffer: String, + + // output / loading + output: Vec, + load_offset: u32, + offset_str: String, + + // cursor - currently unused + cursor_col: usize, + cursor_line: usize, + + // file dialogs + open_file_dialog: Option, + save_file_dialog: Option, + + // other + visible: bool, + error: Option, +} + +impl Component for Editor { + fn name(&self) -> &'static str { + "Editor" + } + + fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + fn category(&self) -> super::interface::Category { + super::interface::Category::Programming + } + + fn render(&mut self, state: &mut State, ui: &mut Ui, ctx: &Context) { + if self.buffer != self.text { + self.unsaved = true; + } + + ui.vertical(|ui| { + // Top bar + + if ui.input(|i| i.key_pressed(Key::S) && i.modifiers.ctrl) { + self.save(); + } + + self.render_toolbar(state, ui, ctx); + + ui.add_space(4.0); // Add some spacing instead of just a separator + ui.separator(); + + let remaining_height = f32::max(ui.available_height() - 100.0, 100.0); + + ui.allocate_ui_with_layout( + egui::Vec2::new(ui.available_width(), remaining_height), + Layout::left_to_right(Align::Min), + |ui| { + self.render_editor(state, ui, ctx); + ui.separator(); + self.render_output(state, ui, ctx); + }, + ); + + self.render_bottom_bar(state, ui, ctx); + }); + } +} + +impl Editor { + #[must_use] + pub const fn new() -> Self { + Self { + path: None, + text: String::new(), + buffer: String::new(), + output: Vec::new(), + unsaved: true, + cursor_col: 1, + cursor_line: 1, + visible: false, + load_offset: 0, + offset_str: String::new(), + error: None, + open_file_dialog: None, + save_file_dialog: None, + } + } + + fn filename(&self) -> &str { + if let Some(path) = &self.path { + return path + .file_name() + .unwrap_or_else(|| OsStr::new("Unnamed!")) + .to_str() + .map_or_else( + || unreachable!("File name should be valid UTF-8."), + |ext| ext, + ); + } + "Unnamed!" + } + + fn extension(&self) -> &str { + if let Some(path) = &self.path { + return path + .extension() + .map_or_else(|| OsStr::new("Unknown!"), |ext| ext) + .to_str() + .map_or_else( + || unreachable!("File name should be valid UTF-8."), + |ext| ext, + ); + } + "Unknown!" + } + + fn save(&mut self) { + if self.open_file_dialog.is_some() { + // TODO: Flash an error stating you can only have one menu open at once. + self.open_file_dialog = None; + } + + if let Some(path) = &self.path { + // Save to existing path + self.buffer = self.text.clone(); + + let text = if path.extension().is_some_and(|ext| ext == "dsb") { + let mut res = Vec::new(); + for line in self.text.lines() { + for line in line.split_whitespace() { + match u32::from_str_radix(line, 16) { + Ok(num) => res.push(num), + Err(e) => { + self.error = Some(format!("Failed to parse file: {e}")); + return; + } + } + } + } + res.into_iter() + .flat_map(u32::to_be_bytes) + .collect::>() + } else { + self.text.as_bytes().to_vec() + }; + + if let Err(why) = std::fs::write(path, text) { + self.error = Some(format!("Failed to save file: {why}")); + } else { + self.unsaved = false; + } + } else { + // Open the save dialog. + let work_dir = std::env::current_dir().unwrap_or_else(|_| { + dirs::home_dir().expect( + "Couldn't get your current working directory or your home directory.", + ) + }); + + if self.save_file_dialog.is_none() { + let mut dialog = FileDialog::save_file(Some(work_dir)); + dialog.open(); + self.save_file_dialog = Some(dialog); + } + } + } + + fn open(&mut self) { + let work_dir = std::env::current_dir().unwrap_or_else(|_| { + dirs::home_dir().expect( + "Couldn't get your current working directory or your home directory.", + ) + }); + + if self.save_file_dialog.is_some() { + // TODO: Flash an error stating you can only have one menu open at once. + self.save_file_dialog = None; + } + + if self.open_file_dialog.is_none() { + if let Some(p) = &self.path { + let path = p.parent().map(Path::to_path_buf); + let mut dialog = FileDialog::open_file(path); + dialog.open(); + self.open_file_dialog = Some(dialog); + } else { + let mut dialog = FileDialog::open_file(Some(work_dir)); + dialog.open(); + self.open_file_dialog = Some(dialog); + } + } + } + + fn handle_file_dialogs(&mut self, ctx: &egui::Context) { + // Handle open dialog + if let Some(dialog) = &mut self.open_file_dialog + && dialog.show(ctx).selected() + { + if let Some(file) = dialog.path() { + // check if the file is a binary file + if file.extension().is_some_and(|ext| ext == "dsb") { + match std::fs::read(file) { + Ok(content) => { + let mut res = String::new(); + for (i, b) in content.iter().enumerate() { + _ = write!(res, "{b:02x}"); + if i % 4 == 3 { + res.push('\n'); + } + } + self.text = res.clone(); + self.buffer = res; + self.path = Some(file.to_path_buf()); + self.unsaved = false; + self.error = None; + } + Err(e) => { + self.error = Some(format!("Failed to read file: {e}")); + } + } + } else { + match std::fs::read_to_string(file) { + Ok(content) => { + self.text = content.clone(); + self.buffer = content; + self.path = Some(file.to_path_buf()); + self.unsaved = false; + self.error = None; + } + Err(e) => { + self.error = Some(format!("Failed to read file: {e}")); + } + } + } + } + self.open_file_dialog = None; + } + + // Handle save dialog + if let Some(dialog) = &mut self.save_file_dialog + && dialog.show(ctx).selected() + { + if let Some(file) = dialog.path() { + self.buffer = self.text.clone(); + + let content = if file.extension().is_some_and(|ext| ext == "dsb") { + let mut res = Vec::new(); + for line in self.text.lines() { + for line in line.split_whitespace() { + match u32::from_str_radix(line, 16) { + Ok(num) => res.push(num), + Err(e) => { + self.error = + Some(format!("Failed to parse file: {e}")); + return; + } + } + } + } + res.into_iter() + .flat_map(u32::to_be_bytes) + .collect::>() + } else { + self.text.clone().as_bytes().to_vec() + }; + + match std::fs::write(file, content) { + Ok(()) => { + self.path = Some(file.to_path_buf()); + self.unsaved = false; + self.error = None; + } + Err(e) => { + self.error = Some(format!("Failed to save file: {e}")); + } + } + } + self.save_file_dialog = None; + } + } + + fn render_output(&self, _state: &mut State, ui: &mut Ui, _ctx: &Context) { + // Output area with synchronized scrolling + egui::ScrollArea::vertical() + .id_salt("output_scroll") + .max_width(400.0) + .show(ui, |ui| { + if self.output.is_empty() { + ui.label( + egui::RichText::new("No output data") + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::GRAY), + ); + return; + } + + egui::Grid::new("output_grid") + .spacing([5.0, 2.0]) // Horizontal and vertical spacing + .num_columns(4) + .striped(false) + .show(ui, |ui| { + // Process bytes in chunks of 4 + for (line_num, chunk) in self.output.chunks(4).enumerate() { + let address = line_num * 4; + + // Convert chunk to u32 (little-endian) + let mut bytes = [0u8; 4]; + for (i, &byte) in chunk.iter().enumerate() { + if i < 4 { + bytes[i] = byte; + } + } + let value = u32::from_be_bytes(bytes); + + // Address column + ui.with_layout( + egui::Layout::left_to_right(egui::Align::Center), + |ui| { + ui.set_min_width(80.0); + let style = ui.style_mut(); + style.visuals.widgets.inactive.bg_fill = + egui::Color32::from_gray(30); + ui.label( + egui::RichText::new(format!("0x{address:04X}")) + .font(egui::FontId::monospace(12.0)), + ); + }, + ); + + // Individual bytes column + let byte_str = chunk + .iter() + .map(|b| format!("{b:02X}")) + .collect::>() + .join(" "); + + ui.label( + egui::RichText::new(format!("{byte_str:<11}")) + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::from_rgb(200, 200, 255)), + ); + + // Hex column + ui.label( + egui::RichText::new(format!("0x{value:08X}")) + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::from_rgb(255, 200, 200)), + ); + + // Instruction column + let instruction = Instruction::decode(value).map_or_else( + |_| format!("{value:10}"), + |instruction| instruction.to_string(), + ); + + ui.label( + egui::RichText::new(instruction) + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::from_rgb(200, 255, 200)), + ); + + ui.end_row(); + } + }); + }); + } + + fn render_editor(&mut self, _state: &mut State, ui: &mut Ui, _ctx: &Context) { + let available_width = ui.available_width(); + let syntax = match self.extension() { + "dsa" => Some(Syntax::new("dsa")), + _ => None, + }; + + let ed = CodeEditor::default() + .id_source("editor") + .with_fontsize(12.0) + .with_rows(0) + .with_theme(ColorTheme::default()) + .with_syntax(Syntax::dsa()) + .with_numlines(true) + .desired_width(available_width - 500.0); + + let mut editor = ed.clone(); + + if let Some(syntax) = syntax { + editor = ed.with_syntax(syntax); + } + + editor.show(ui, &mut self.text); + } + + fn render_bottom_bar(&self, _state: &mut State, ui: &mut Ui, _ctx: &Context) { + ui.horizontal(|ui| { + // error display + ui.label( + egui::RichText::new(self.error.clone().unwrap_or_default()) + .color(egui::Color32::RED), + ); + + // line and col + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.label(format!("Ln {}, Col {}", self.cursor_line, self.cursor_col)); + }); + }); + } + + fn build(&mut self) { + if let Some(path) = &self.path { + match path.extension().and_then(|ext| ext.to_str()) { + Some("dsa") => { + let mut compiler = CompilerEngine::new(); + compiler.start_compilation(path); + + // Or block until done + let instructions = match compiler.wait_for_result() { + Ok(instructions) => instructions, + Err(e) => { + self.error = Some(e.to_string()); + return; + } + }; + + self.output = instructions + .iter() + .flat_map(|i| i.encode().to_be_bytes().to_vec()) + .collect(); + } + Some("dsb") => { + if let Ok(bytes) = fs::read(path) { + self.output = bytes; + } else { + self.error = Some("Failed to read file".to_string()); + } + } + _ => { + self.error = Some(format!("Invalid file type: {}", self.filename())); + } + } + } + } + + fn render_toolbar(&mut self, state: &State, ui: &mut Ui, ctx: &Context) { + self.handle_file_dialogs(ctx); + + ui.horizontal(|ui| { + ui.label(format!("File type: {}", self.extension())); + ui.label(format!("Filename: {}", self.filename())); + ui.label(format!("Unsaved: {}", self.unsaved)); + + // number of lines in the file + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + let line_count = self.text.lines().count(); + ui.label(format!("Lines: {line_count}")); + }); + }); + + ui.horizontal(|ui| { + ui.spacing_mut().button_padding = egui::vec2(8.0, 4.0); + ui.spacing_mut().item_spacing.x = 6.0; + + // Opens a file + if ui.button("Open").clicked() { + self.open(); + } + + // Saves the current file + if ui.button("Save").clicked() { + self.save(); + } + + // builds the current file + if ui.button("Build").clicked() && !self.unsaved { + self.build(); + } + + // Loads the generated binary into the assembler at the provided offset + if ui.button("Load").clicked() { + if self.error.is_some() { + self.error = + Some("Can't load program at invalid offset!".to_string()); + } + + state + .cmd_sender + .send(Command::Write(self.load_offset, self.output.clone())) + .unwrap_or_else(|_| { + self.error = Some("Failed to send command".to_string()); + }); + } + + // Entry widget to enter a load offset + if ui.text_edit_singleline(&mut self.offset_str).changed() { + if let Some(offset) = parse_address(&self.offset_str) { + self.load_offset = offset; + self.error = None; + } else { + self.error = Some("Invalid offset".to_string()); + } + } + }); + } +} + +fn parse_address(address: &str) -> Option { + address.strip_prefix("0x").map_or_else( + || { + address.strip_prefix("0b").map_or_else( + || { + address.strip_prefix("0o").map_or_else( + || address.parse::().ok(), + |oct| u32::from_str_radix(oct, 8).ok(), + ) + }, + |bin| u32::from_str_radix(bin, 2).ok(), + ) + }, + |hex| u32::from_str_radix(hex, 16).ok(), + ) +} diff --git a/emulator/src/emulator/ui/history.rs b/emulator/src/emulator/ui/history.rs new file mode 100644 index 0000000..64be793 --- /dev/null +++ b/emulator/src/emulator/ui/history.rs @@ -0,0 +1,84 @@ +use egui::{Context, Ui}; + +use crate::emulator::{ + system::model::{Command, State}, + ui::interface::Component, +}; + +pub struct History { + visible: bool, +} + +impl Component for History { + fn name(&self) -> &'static str { + "Instruction History" + } + + fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + fn category(&self) -> super::interface::Category { + super::interface::Category::Control + } + + fn render(&mut self, state: &mut State, ui: &mut Ui, _ctx: &Context) { + state.send(Command::HistoryRequest); + + egui::ScrollArea::vertical() + .id_salt("output_scroll") + .max_width(400.0) + .show(ui, |ui| { + if state.instruction_history.is_empty() { + ui.label( + egui::RichText::new("No output data") + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::GRAY), + ); + return; + } + + egui::Grid::new("output_grid") + .spacing([5.0, 2.0]) // Horizontal and vertical spacing + .num_columns(4) + .striped(false) + .show(ui, |ui| { + // Process bytes in chunks of 4 + for (idx, instruction) in + state.instruction_history.iter().enumerate() + { + ui.label(format!("{idx}: ")); + + // Hex column + let addr = instruction.0; + ui.label( + egui::RichText::new(format!("0x{addr:08X}")) + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::from_rgb(255, 200, 200)), + ); + + ui.label( + egui::RichText::new(instruction.1.to_string()) + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::from_rgb(200, 255, 200)), + ); + + ui.end_row(); + } + }); + }); + } +} + +impl Default for History { + fn default() -> Self { + Self::new() + } +} + +impl History { + #[must_use] + pub const fn new() -> Self { + Self { visible: false } + } +} diff --git a/emulator/src/emulator/ui/interface.rs b/emulator/src/emulator/ui/interface.rs new file mode 100644 index 0000000..f5b85c9 --- /dev/null +++ b/emulator/src/emulator/ui/interface.rs @@ -0,0 +1,128 @@ +use crate::emulator::system::model::{Command, Running, State, StateUpdate}; +use std::sync::mpsc::{Receiver, Sender}; + +pub trait Component { + fn render(&mut self, state: &mut State, ui: &mut egui::Ui, ctx: &egui::Context); + fn visible(&mut self) -> &mut bool; + fn name(&self) -> &'static str; + fn category(&self) -> Category; +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Category { + Control, + Memory, + IO, + Programming, +} + +impl Category { + #[must_use] + pub const fn as_str(&self) -> &'static str { + match self { + Self::Control => "Control Systems", + Self::Memory => "Memory Systems", + Self::IO => "I/O Systems", + Self::Programming => "Programming", + } + } + + #[must_use] + pub fn list() -> Vec { + vec![Self::Control, Self::Memory, Self::IO, Self::Programming] + } +} + +pub struct EmulatorUI { + pub state: State, + pub components: Vec>, +} + +impl EmulatorUI { + #[must_use] + pub fn new(sender: Sender, receiver: Receiver) -> Self { + Self { + state: State::new(sender, receiver), + components: vec![], + } + } + + pub fn add_component(&mut self, component: Box) { + self.components.push(component); + } +} + +impl eframe::App for EmulatorUI { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + if let Err(e) = self.state.update() { + self.state.error_log.push(e.to_string()); + } + + if self.state.running == Running::Running { + ctx.request_repaint(); + } + + egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { + ui.with_layout( + egui::Layout::top_down_justified(egui::Align::Center) + .with_main_align(egui::Align::Min), + |ui| { + ui.allocate_space(egui::vec2(0.0, 15.0)); + ui.heading("DSA Simulator (Damn Simple Architecture 🔥)"); + ui.allocate_space(egui::vec2(0.0, 15.0)); + }, + ); + }); + + egui::CentralPanel::default().show(ctx, |ui| { + ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |_ui| { + egui::Window::new("Main Menu") + .resizable(false) + .default_width(300.0) + .show(ctx, |ui| { + super::menu::render_menu(self, ui, ctx); + }); + + for c in &mut self.components { + let mut visible = *c.visible(); + if visible { + egui::Window::new(c.name()) + .open(&mut visible) + .show(ctx, |ui| { + c.render(&mut self.state, ui, ctx); + }); + } + *c.visible() = visible; + } + }); + }); + + egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| { + ui.horizontal_centered(|ui| { + ui.group(|ui| { + ui.add_space(10.0); + ui.strong("Authors:"); + ui.add_space(5.0); + ui.label("zxq5"); + ui.label("nullndvoid"); + ui.add_space(10.0); + ui.separator(); + ui.add_space(10.0); + ui.strong("Version"); + ui.add_space(5.0); + ui.label("1.0.0"); + ui.add_space(10.0); + ui.separator(); + ui.add_space(10.0); + ui.strong("Source:"); + ui.add_space(5.0); + ui.hyperlink_to( + "https://git.zxq5.dev/LowLevelDevs/damn_simple_architecture", + "https://git.zxq5.dev/LowLevelDevs/damn_simple_architecture", + ); + ui.add_space(10.0); + }); + }); + }); + } +} diff --git a/emulator/src/emulator/ui/loader.rs b/emulator/src/emulator/ui/loader.rs new file mode 100644 index 0000000..75b2bdd --- /dev/null +++ b/emulator/src/emulator/ui/loader.rs @@ -0,0 +1,294 @@ +use std::{ + ffi::OsStr, + path::{Path, PathBuf}, +}; + +use common::prelude::Instruction; +use egui::{Context, Ui}; +use egui_file::FileDialog; + +use crate::emulator::{ + system::model::{Command, State}, + ui::interface::Component, +}; + +#[derive(Default)] +pub struct Loader { + path: Option, + output: Vec, + load_offset: u32, + offset_str: String, + + // file dialogs + open_file_dialog: Option, + + // other + visible: bool, + error: Option, +} + +impl Component for Loader { + fn name(&self) -> &'static str { + "Loader" + } + + fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + fn category(&self) -> super::interface::Category { + super::interface::Category::Programming + } + + fn render(&mut self, state: &mut State, ui: &mut Ui, ctx: &Context) { + ui.vertical(|ui| { + self.render_toolbar(state, ui, ctx); + + ui.add_space(4.0); // Add some spacing instead of just a separator + ui.separator(); + + egui::ScrollArea::vertical() + .auto_shrink([false; 2]) + .max_height(ui.available_height() - 100.0) + .show(ui, |ui| { + self.render_output(state, ui, ctx); + }); + + self.render_bottom_bar(state, ui, ctx); + }); + } +} + +impl Loader { + #[must_use] + pub const fn new() -> Self { + Self { + path: None, + output: Vec::new(), + visible: false, + load_offset: 0, + offset_str: String::new(), + error: None, + open_file_dialog: None, + } + } + + fn filename(&self) -> &str { + if let Some(path) = &self.path { + return path + .file_name() + .unwrap_or_else(|| OsStr::new("Unnamed!")) + .to_str() + .map_or_else( + || unreachable!("File name should be valid UTF-8."), + |ext| ext, + ); + } + "Unnamed!" + } + + fn open(&mut self) { + let work_dir = std::env::current_dir().unwrap_or_else(|_| { + dirs::home_dir().expect( + "Couldn't get your current working directory or your home directory.", + ) + }); + + if self.open_file_dialog.is_some() { + // TODO: Flash an error stating you can only have one menu open at once. + self.open_file_dialog = None; + } + + if self.open_file_dialog.is_none() { + if let Some(p) = &self.path { + let path = p.parent().map(Path::to_path_buf); + let mut dialog = FileDialog::open_file(path); + dialog.open(); + self.open_file_dialog = Some(dialog); + } else { + let mut dialog = FileDialog::open_file(Some(work_dir)); + dialog.open(); + self.open_file_dialog = Some(dialog); + } + } + } + + fn handle_file_dialogs(&mut self, ctx: &egui::Context) { + // Handle open dialog + if let Some(dialog) = &mut self.open_file_dialog + && dialog.show(ctx).selected() + { + if let Some(file) = dialog.path() { + // check if the file is a binary file + if file.extension().is_some_and(|ext| ext == "dsb") { + match std::fs::read(file) { + Ok(content) => { + self.output = content; + self.error = None; + } + Err(e) => { + self.error = Some(format!("Failed to read file: {e}")); + } + } + } + } + self.open_file_dialog = None; + } + } + + fn render_output(&self, _state: &mut State, ui: &mut Ui, _ctx: &Context) { + // Output area with synchronized scrolling + egui::ScrollArea::vertical() + .id_salt("output_scroll") + .max_width(400.0) + .show(ui, |ui| { + if self.output.is_empty() { + ui.label( + egui::RichText::new("No output data") + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::GRAY), + ); + return; + } + + egui::Grid::new("output_grid") + .spacing([5.0, 2.0]) // Horizontal and vertical spacing + .num_columns(4) + .striped(false) + .show(ui, |ui| { + // Process bytes in chunks of 4 + for (line_num, chunk) in self.output.chunks(4).enumerate() { + let address = line_num * 4; + + // Convert chunk to u32 (little-endian) + let mut bytes = [0u8; 4]; + for (i, &byte) in chunk.iter().enumerate() { + if i < 4 { + bytes[i] = byte; + } + } + let value = u32::from_be_bytes(bytes); + + // Address column + ui.with_layout( + egui::Layout::left_to_right(egui::Align::Center), + |ui| { + ui.set_min_width(80.0); + let style = ui.style_mut(); + style.visuals.widgets.inactive.bg_fill = + egui::Color32::from_gray(30); + ui.label( + egui::RichText::new(format!("0x{address:04X}")) + .font(egui::FontId::monospace(12.0)), + ); + }, + ); + + // Individual bytes column + let byte_str = chunk + .iter() + .map(|b| format!("{b:02X}")) + .collect::>() + .join(" "); + + ui.label( + egui::RichText::new(format!("{byte_str:<11}")) + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::from_rgb(200, 200, 255)), + ); + + // Hex column + ui.label( + egui::RichText::new(format!("0x{value:08X}")) + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::from_rgb(255, 200, 200)), + ); + + // Instruction column + let instruction = Instruction::decode(value).map_or_else( + |_| format!("{value:10}"), + |instruction| instruction.to_string(), + ); + + ui.label( + egui::RichText::new(instruction) + .font(egui::FontId::monospace(12.0)) + .color(egui::Color32::from_rgb(200, 255, 200)), + ); + + ui.end_row(); + } + }); + }); + } + + fn render_bottom_bar(&self, _state: &mut State, ui: &mut Ui, _ctx: &Context) { + ui.horizontal(|ui| { + // error display + ui.label( + egui::RichText::new(self.error.clone().unwrap_or_default()) + .color(egui::Color32::RED), + ); + }); + } + + fn render_toolbar(&mut self, state: &State, ui: &mut Ui, ctx: &Context) { + self.handle_file_dialogs(ctx); + + ui.horizontal(|ui| { + ui.label(format!("Filename: {}", self.filename())); + }); + + ui.horizontal(|ui| { + ui.spacing_mut().button_padding = egui::vec2(8.0, 4.0); + ui.spacing_mut().item_spacing.x = 6.0; + + // Opens a file + if ui.button("Open").clicked() { + self.open(); + } + + // Loads the generated binary into the assembler at the provided offset + if ui.button("Load").clicked() { + if self.error.is_some() { + self.error = + Some("Can't load program at invalid offset!".to_string()); + } + + state + .cmd_sender + .send(Command::Write(self.load_offset, self.output.clone())) + .unwrap_or_else(|_| { + self.error = Some("Failed to send command".to_string()); + }); + } + + // Entry widget to enter a load offset + if ui.text_edit_singleline(&mut self.offset_str).changed() { + if let Some(offset) = parse_address(&self.offset_str) { + self.load_offset = offset; + self.error = None; + } else { + self.error = Some("Invalid offset".to_string()); + } + } + }); + } +} + +fn parse_address(address: &str) -> Option { + address.strip_prefix("0x").map_or_else( + || { + address.strip_prefix("0b").map_or_else( + || { + address.strip_prefix("0o").map_or_else( + || address.parse::().ok(), + |oct| u32::from_str_radix(oct, 8).ok(), + ) + }, + |bin| u32::from_str_radix(bin, 2).ok(), + ) + }, + |hex| u32::from_str_radix(hex, 16).ok(), + ) +} diff --git a/emulator/src/emulator/ui/memory_inspector.rs b/emulator/src/emulator/ui/memory_inspector.rs new file mode 100644 index 0000000..d6206d1 --- /dev/null +++ b/emulator/src/emulator/ui/memory_inspector.rs @@ -0,0 +1,162 @@ +use std::num::ParseIntError; + +use common::prelude::Instruction; + +use crate::emulator::{ + system::model::{Command, State}, + ui::interface::Component, +}; + +#[derive(Default)] +pub struct MemoryInspector { + view_size: u32, + view_addr: u32, + visible: bool, + addr_input: String, +} + +impl MemoryInspector { + #[must_use] + pub const fn new() -> Self { + Self { + view_size: 256, + view_addr: 0, + visible: false, + addr_input: String::new(), + } + } +} + +impl Component for MemoryInspector { + fn category(&self) -> super::interface::Category { + super::interface::Category::Memory + } + + fn name(&self) -> &'static str { + "Memory Inspector" + } + + fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + fn render(&mut self, state: &mut State, ui: &mut egui::Ui, ctx: &egui::Context) { + // Right column - Memory + ui.vertical(|ui| { + ui.heading("Memory Inspector"); + ui.add_space(10.0); + + // Address input section + ui.horizontal(|ui| { + ui.label("Address:"); + + let address_response = ui.add( + egui::TextEdit::singleline(&mut self.addr_input) + .hint_text("0x1000 or 4096") + .desired_width(150.0), + ); + + ui.add_space(10.0); + + // Search button + let search_clicked = ui.button("🔍 Search").clicked(); + + // Handle Enter key in text field + let enter_pressed = address_response.lost_focus() + && ctx.input(|i| i.key_pressed(egui::Key::Enter)); + + if search_clicked || enter_pressed { + if let Ok(new) = parse_address(&self.addr_input) { + self.view_addr = new; + } else { + state.error_log.push("Invalid address".to_string()); + } + } + + let _ = state + .cmd_sender + .send(Command::MemRequest(self.view_addr, self.view_size)); + + ui.label("(hex or decimal)"); + }); + + // Show input error if any + if let Some(error) = state.error_log.last() { + ui.colored_label(egui::Color32::RED, format!("Error: {error}")); + } + + ui.add_space(10.0); + + // Memory table + egui::ScrollArea::vertical() + .auto_shrink(true) + .id_salt("memory_inspector_scroll") + .show(ui, |ui| { + egui::Grid::new("memory_grid") + .spacing([12.0, 2.0]) + .min_col_width(5.0) + .striped(true) + .show(ui, |ui| { + // Header + ui.strong("Address"); + + for i in 0..4 { + ui.strong(format!("{i:X}")); + } + + ui.strong("Decimal"); + ui.strong("Instruction"); + + ui.end_row(); + + // Memory data (8 bytes per row) + for (row, chunk) in (0u32..).zip(state.memory_view.chunks(4)) + { + let row_address = self.view_addr + (row * 4); + ui.monospace(format!( + "0x{row_address:08X} ({row_address})" + )); + for &byte in chunk { + ui.monospace(format!("{byte:02X}")); + } + + // Fill remaining columns if last row is incomplete + for _ in chunk.len()..4 { + ui.label(""); + } + + // combine all 4 bytes in the chunk into a u32 + let combined = chunk.iter().fold(0u32, |acc, &byte| { + (acc << 8) | u32::from(byte) + }); + + ui.monospace(format!("{combined}")); + ui.monospace(format!( + "{}", + Instruction::decode(combined) + .unwrap_or(Instruction::Nop) + )); + + ui.end_row(); + } + }); + }); + }); + } +} + +fn parse_address(address: &str) -> Result { + if let Some(hex_part) = address.strip_prefix("0x") { + return u32::from_str_radix(hex_part, 16); + } + + if let Some(bin_part) = address.strip_prefix("0b") { + return u32::from_str_radix(bin_part, 2); + } + + if let Some(oct_part) = address.strip_prefix("0o") { + return u32::from_str_radix(oct_part, 8); + } + + address.parse::() +} diff --git a/emulator/src/emulator/ui/menu.rs b/emulator/src/emulator/ui/menu.rs new file mode 100644 index 0000000..edd5008 --- /dev/null +++ b/emulator/src/emulator/ui/menu.rs @@ -0,0 +1,30 @@ +use crate::emulator::ui::interface::{Category, EmulatorUI}; + +pub fn render_menu(state: &mut EmulatorUI, ui: &mut egui::Ui, _ctx: &egui::Context) { + ui.with_layout( + egui::Layout::top_down_justified(egui::Align::Center), + |ui| { + ui.set_max_width(300.0); + ui.set_min_width(300.0); + ui.spacing_mut().button_padding = egui::vec2(10.0, 5.0); + + for cat in Category::list() { + ui.add_space(10.0); + ui.heading(cat.as_str()); + ui.add_space(10.0); + + for comp in &mut state.components { + let name = comp.name(); + if comp.category() == cat { + ui.toggle_value(comp.visible(), name); + } + } + + ui.add_space(10.0); + ui.separator(); + } + + ui.add_space(10.0); + }, + ); +} diff --git a/emulator/src/emulator/ui/mod.rs b/emulator/src/emulator/ui/mod.rs new file mode 100644 index 0000000..9e0cf7e --- /dev/null +++ b/emulator/src/emulator/ui/mod.rs @@ -0,0 +1,9 @@ +pub mod control_unit; +pub mod display; +pub mod editor; +pub mod history; +pub mod interface; +pub mod loader; +pub mod memory_inspector; +pub mod menu; +pub mod stack_inspector; diff --git a/emulator/src/emulator/ui/stack_inspector.rs b/emulator/src/emulator/ui/stack_inspector.rs new file mode 100644 index 0000000..b89f4f0 --- /dev/null +++ b/emulator/src/emulator/ui/stack_inspector.rs @@ -0,0 +1,79 @@ +use crate::emulator::{ + system::model::{Command, State}, + ui::interface::Component, +}; + +use common::instructions::Register; + +pub struct StackInspector { + visible: bool, +} + +impl Default for StackInspector { + fn default() -> Self { + Self::new() + } +} + +impl StackInspector { + #[must_use] + pub const fn new() -> Self { + Self { visible: false } + } +} + +impl Component for StackInspector { + fn visible(&mut self) -> &mut bool { + &mut self.visible + } + + fn name(&self) -> &'static str { + "Stack Inspector" + } + + fn category(&self) -> super::interface::Category { + super::interface::Category::Memory + } + + fn render(&mut self, state: &mut State, ui: &mut egui::Ui, _ctx: &egui::Context) { + state.send(Command::StackRequest); + + ui.vertical(|ui| { + ui.heading("Stack Inspector"); + egui::ScrollArea::vertical() + .id_salt("stack_inspector_scroll") + .show(ui, |ui| { + egui::Grid::new("stack_grid") + .num_columns(2) + .spacing([40.0, 4.0]) + .striped(true) + .show(ui, |ui| { + ui.label("Address"); + ui.label("Value"); + ui.end_row(); + + for (i, value) in + state.stack_view.chunks(4).take(32).enumerate() + { + let value = u32::from_be_bytes(value.try_into().expect( + "Could not read 4 byte instruction or data! Something is wrong.", + )); + ui.label(format!( + "{} [{}]", + i, + state.reg_file.get(Register::Spr).expect("SPR should never be invalid") - i as u32 * 4 + )); + ui.label(format!("0x{value:08X} ({value})")); + ui.end_row(); + } + + if state.stack_view.is_empty() { + ui.label("(empty)"); + ui.label("-"); + ui.end_row(); + } + }); + }); + }); + } +} diff --git a/emulator/src/lib.rs b/emulator/src/lib.rs new file mode 100644 index 0000000..32ce805 --- /dev/null +++ b/emulator/src/lib.rs @@ -0,0 +1,132 @@ +#![deny( + clippy::unwrap_used, + clippy::nursery, + clippy::perf, + clippy::pedantic, + clippy::complexity +)] +#![allow( + clippy::cast_possible_truncation, + clippy::missing_panics_doc, + clippy::missing_errors_doc, + clippy::match_wildcard_for_single_variants +)] + +pub mod emulator; + +use std::{ + sync::{ + Arc, + mpsc::{Receiver, Sender}, + }, + thread, +}; + +#[cfg(target_os = "android")] +use winit::platform::android::{EventLoopBuilderExtAndroid, activity::AndroidApp}; + +use crate::emulator::{ + misc::rpc::RpcClient, + system::{ + emulator::run_emulator, + memory::MainStore, + model::{Command, StateUpdate}, + processor::Processor, + }, + ui::{ + control_unit::ControlPanel, display::Display, editor::Editor, + interface::EmulatorUI, memory_inspector::MemoryInspector, + stack_inspector::StackInspector, + }, +}; + +#[cfg(target_os = "android")] +#[unsafe(no_mangle)] +pub fn android_main(app: AndroidApp) -> Result<(), Box> { + use crate::emulator::{config::Config, misc::rpc::get_rpc_client_or_none}; + use std::path::Path; + + // Initialize channels and read in configuration. + let (cmd_sender, cmd_receiver) = std::sync::mpsc::channel(); + let (state_sender, state_reciever) = std::sync::mpsc::channel(); + let config = Config::load(Path::new(".dsa.emulator.toml"))?; + + // Setup RPC if enabled. + let (rpc_sender, rpc_reciever) = std::sync::mpsc::channel(); + + let rpc_client = + get_rpc_client_or_none(&config, rpc_sender, rpc_reciever)?.map(Arc::new); + + setup_emulator(cmd_receiver, state_sender, rpc_client); + + let ui = setup_ui(cmd_sender, state_reciever); + + // Run UI. + #[allow(unused_variables)] + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default().with_inner_size([800.0, 600.0]), + event_loop_builder: Some(Box::new(move |builder| { + #[cfg(target_os = "android")] + builder.with_android_app(app); + })), + ..Default::default() + }; + + eframe::run_native( + "DSA Simulator (Damn Simple Architecture 🔥)", + options, + Box::new(move |cc| { + cc.egui_ctx.set_visuals(egui::Visuals::default()); + Ok(Box::new(ui)) + }), + )?; + + Ok(()) +} + +pub fn setup_emulator( + cmd_receiver: Receiver, + state_sender: Sender, + rpc_client: Option>, +) { + let main_store = MainStore::new(); + let processor = Processor::new(Box::new(main_store), vec![]); + + thread::spawn(move || { + run_emulator(&cmd_receiver, &state_sender, processor, rpc_client.as_ref()); + }); +} + +/// Creates the [`EmulatorUI`]. +#[must_use] +pub fn setup_ui( + cmd_sender: Sender, + state_reciever: Receiver, +) -> EmulatorUI { + let mut ui = EmulatorUI::new(cmd_sender, state_reciever); + + // Create UI modules. + let control_unit = ControlPanel::new(); + + ui.add_component(Box::new(control_unit)); + + let mem_inspector = MemoryInspector::new(); + ui.add_component(Box::new(mem_inspector)); + + let stack_inspector = StackInspector::new(); + ui.add_component(Box::new(stack_inspector)); + + let editor = Editor::new(); + ui.add_component(Box::new(editor)); + + let display = Display::new(); + ui.add_component(Box::new(display)); + + let history = emulator::ui::history::History::new(); + ui.add_component(Box::new(history)); + + let loader = emulator::ui::loader::Loader::new(); + ui.add_component(Box::new(loader)); + + ui +} diff --git a/emulator/src/main.rs b/emulator/src/main.rs new file mode 100644 index 0000000..b074df9 --- /dev/null +++ b/emulator/src/main.rs @@ -0,0 +1,39 @@ +use std::path::Path; +use std::sync::Arc; + +use dsa_rs::emulator::{config::Config, misc::rpc::get_rpc_client_or_none}; + +fn main() -> Result<(), Box> { + // Initialize channels and read in configuration. + let (cmd_sender, cmd_receiver) = std::sync::mpsc::channel(); + let (state_sender, state_reciever) = std::sync::mpsc::channel(); + let config = Config::load(Path::new(".dsa.emulator.toml"))?; + + // Setup RPC if enabled. + let (rpc_sender, rpc_reciever) = std::sync::mpsc::channel(); + + let rpc_client = + get_rpc_client_or_none(&config, rpc_sender, rpc_reciever)?.map(Arc::new); + + dsa_rs::setup_emulator(cmd_receiver, state_sender, rpc_client); + + let ui = dsa_rs::setup_ui(cmd_sender, state_reciever); + + // Run UI. + #[allow(unused_variables)] + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default().with_inner_size([800.0, 600.0]), + ..Default::default() + }; + + eframe::run_native( + "DSA Simulator (Damn Simple Architecture 🔥)", + options, + Box::new(move |cc| { + cc.egui_ctx.set_visuals(egui::Visuals::default()); + Ok(Box::new(ui)) + }), + )?; + + Ok(()) +} diff --git a/resources/dsa/bf.dsa b/resources/dsa/bf.dsa new file mode 100644 index 0000000..57e39d9 --- /dev/null +++ b/resources/dsa/bf.dsa @@ -0,0 +1,224 @@ + +// a simple brainf##k interpreter, +// because I already wrote a compiler lol. + +include print "./lib/io/print.dsa" + +// "print hello world" +db program: "++++++++++++++++++++++++++++++++++++++++++++ +>++++++++++++++++++++++++++++++++ +>++++++++++++++++ +> +>+ +<< +[ +>> +> +>++++++++++ +<< +[->+>-[>+>>]>[+[-<+>]>+>>]<<<<<<] +>[<+>-] +>[-] +>> +>++++++++++ +< +[->-[>+>>]>[+[-<+>]>+>>]<<<<<] +>[-] +>>[++++++++++++++++++++++++++++++++++++++++++++++++.[-]] +<[++++++++++++++++++++++++++++++++++++++++++++++++.[-]] +<<<++++++++++++++++++++++++++++++++++++++++++++++++.[-] +<<<<<<<.>. +>>[>>+<<-] +>[>+<<+>-] +>[<+>-] +<<<- +] +<<++..." + +db error: "Invalid Instruction!" +dw stack: 0x10000 +dw input: 0x30000 +resb data: 1024 + +// set up a stack so we can call functions +_init_stack: + ldw stack, bpr + mov bpr, spr + +start: + // load the start of the program into rg0 + lwi program, rg0 + lwi data, rg1 + + // rg0 is our instruction pointer + // rg1 is our data pointer + // rg2 is the value at the data pointer + // rg3 stores the current instruction + // rg4 is the expression nesting level. + + lli 43, rg8 // + = 43 increment + lli 45, rg9 // - = 45 decrement + lli 62, rga // > = 62 increment pointer + lli 60, rgb // < = 60 decrement pointer + lli 46, rgc // . = 46 output + lli 44, rgd // , = 44 input + lli 91, rge // [ = 91 loop start + lli 93, rgf // ] = 93 loop end + +loop_start: + // load the current instruction into rg3 + ldb rg0, rg3 + + // switch on the instruction + // all cases will return to either loop_start or loop_end + cmp rg3, rg8 + jeq increment + cmp rg3, rg9 + jeq decrement + cmp rg3, rga + jeq inc_ptr + cmp rg3, rgb + jeq dec_ptr + cmp rg3, rgc + jeq output + cmp rg3, rgd + jeq input + cmp rg3, rge + jeq expr_start + cmp rg3, rgf + jeq expr_end + cmp rg3, zero + jeq end + + // if we get here, we don't know what the instruction is + lwi error, rg2 + pusha 2 + push rg2 + call print::print + pop zero + popa 2 + +end: + hlt + +loop_end: + inc rg0 + jmp loop_start + +// ------------------------------------------ +// increment the current cell +increment: + inc rg2 + jmp loop_end + +// ------------------------------------------ +// decrement the current cell +decrement: + dec rg2 + jmp loop_end + +// ------------------------------------------ +// increment the pointer +inc_ptr: + stw rg2, rg1 + addi rg1, 4 + ldw rg1, rg2 + jmp loop_end + +// ------------------------------------------ +// decrement the pointer +dec_ptr: + stw rg2, rg1 + subi rg1, 4 + ldw rg1, rg2 + jmp loop_end + +// ------------------------------------------ +// print the byte in the current cell +output: + pusha 2 + push rg2 + call print::print_byte + pop zero + popa 2 + jmp loop_end + +// ------------------------------------------ +// read a byte into the current cell +input: + ldw input, rg2 + jmp loop_end + +// ------------------------------------------ +// handle an open bracket instruction +expr_start: + cmp rg2, zero + jne loop_end + +_traverse_right_start: + // push a register that definitely has a nonzero value + // when we pop this value from the stack + // we know we've finished traversing. + push rg8 + +_traverse_right: + inc rg0 + ldb rg0, rg3 + + cmp rg3, rge + jeq open_right + cmp rg3, rgf + jeq close_right + cmp rg3, zero + jeq end + jmp _traverse_right + +open_right: + // push zero to the stack + push zero + jmp _traverse_right + +close_right: + // check if we've reached the bottom of the stack + pop rg4 + cmp rg4, zero + jeq _traverse_right + + // go to next instruction after closing bracket + inc rg0 + jmp loop_start + +// ------------------------------------------ +// handle the close bracket instruction +expr_end: + cmp rg2, zero + jeq loop_end + +_traverse_left_start: + push rg8 +_traverse_left: + dec rg0 + ldb rg0, rg3 + + cmp rg3, rge + jeq open_left + cmp rg3, rgf + jeq close_left + cmp rg3, zero + jeq end + jmp _traverse_left + +open_left: + // check if we've reached the bottom of the stack + pop rg4 + cmp rg4, zero + jeq _traverse_left + + // go to next instruction after open bracket + inc rg0 + jmp loop_start + +close_left: + // push zero to the stack + push zero + jmp _traverse_left diff --git a/resources/dsa/lib/collections/stack.dsa b/resources/dsa/lib/collections/stack.dsa new file mode 100644 index 0000000..e69de29 diff --git a/resources/dsa/lib/error/handlers.dsa b/resources/dsa/lib/error/handlers.dsa new file mode 100644 index 0000000..418cef2 --- /dev/null +++ b/resources/dsa/lib/error/handlers.dsa @@ -0,0 +1,34 @@ +include print "../io/print.dsa" + +dw idt: 0xFFFF0000 + +setup_idt: + push bpr + mov spr, bpr + + // load the IDT into the IDR + ldw idt, idr + + mov bpr, spr + pop bpr + irt + +setup_hard_fault_handler: + push bpr + mov spr, bpr + + lwi handle_hard_fault, rg0 + stw rg0, idr, 4 + + mov bpr, spr + pop bpr + irt + +dw hard_fault_err: "FATAL: Illegal Instruction or Memory Access!" +handle_hard_fault: + call print::reset + lwi hard_fault_err, rg0 + push rg0 + call print::print + pop zero + hlt diff --git a/resources/dsa/lib/io/print.dsa b/resources/dsa/lib/io/print.dsa new file mode 100644 index 0000000..377d438 --- /dev/null +++ b/resources/dsa/lib/io/print.dsa @@ -0,0 +1,244 @@ +// lib: +// print.dsa + +// usage: +// +// include print """ +// +// usage for print: +// push (register containing address of string) +// push pcx +// jmp print::print +// +// usage for reset: +// push pcx +// jmp print::reset +// +// usage for clear: +// push pcx +// jmp print::clear +// +// usage for print_byte: +// push (register containing byte) +// push pcx +// jmp print::print_byte +// +// usage for print_word: +// push (register containing word) +// push pcx +// jmp print::print_word +// + +include maths "../maths/core.dsa" + +dw display: 0x20000 +dw current: 0x20000 + +// ------------------------------------------ +// prints the string at addr(arg[0]) to the screen. +print: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + ldw current, rg1 + +_print_loop: + ldb rg0, acc + stb acc, rg1 + + addi rg0, 1 + addi rg1, 1 + + cmp acc, zero + jne _print_loop + jmp _end + +// ------------------------------------------ +// prints the value of arg[0] to the screen. +print_word: + // initialise + push bpr + mov spr, bpr + + // load byte into acc + ldw bpr, rg0, 8 + ldw current, rg1 + + addi rg1, 3 + + stb rg0, rg1 + subi rg1, 1 + shr rg0, 8 + stb rg0, rg1 + subi rg1, 1 + shr rg0, 8 + stb rg0, rg1 + subi rg1, 1 + shr rg0, 8 + stb rg0, rg1 + + addi rg1, 4 + jmp _end + +// ------------------------------------------ +// prints the last byte of arg[0] to the screen. +print_byte: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + ldw current, rg1 + + stb rg0, rg1 + addi rg1, 1 + jmp _end + +// ------------------------------------------ +// prints the value of arg[0] to the screen in hex. +print_hex_word: + push bpr + mov spr, bpr + + ldw current, rg1 + + ldb bpr, rg0, 8 + push rg0 + call _print_hex_byte + addi spr, 4 + + ldb bpr, rg0, 9 + push rg0 + call _print_hex_byte + addi spr, 4 + + ldb bpr, rg0, 10 + push rg0 + call _print_hex_byte + addi spr, 4 + + ldb bpr, rg0, 11 + push rg0 + call _print_hex_byte + addi spr, 4 + + jmp _end + +// ------------------------------------------ +// prints the last byte of arg[0] to the screen in hex. +print_hex_byte: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 + ldw current, rg1 + + call _print_hex_byte + jmp _end + +// function body +_print_hex_byte: + // mask to get lower nibble + lli 0xF, rg2 + // save rg0 state + push rg0 + + shr rg0, 4 + and rg0, rg2, rg0 + call _print_hex_nibble + pop rg0 + + and rg0, rg2, rg0 + call _print_hex_nibble + return + +// print a hex digit +_print_hex_nibble: + lli 10, rg3 + cmp rg0, rg3 + jlt _print_hex_nibble_number + addi rg0, 0x37, rg0 + stb rg0, rg1 + addi rg1, 1 + return + +// helper function. +_print_hex_nibble_number: + addi rg0, 0x30, rg0 + stb rg0, rg1 + addi rg1, 1 + return + +// ------------------------------------------ +// print whitespace +print_whitespace: + push bpr + mov spr, bpr + + ldw current, rg1 + lli 0x20, rg0 + stb rg0, rg1 + addi rg1, 1 + jmp _end + +// ------------------------------------------ +// print newline +print_newline: + push bpr + mov spr, bpr + + // load variables into registers + ldw display, rg0 + ldw current, rg1 + + // get the offset from the display base + sub rg1, rg0, rg0 + + lwi 80, rg2 + pusha 3 + push rg0 + push rg2 + call maths::divmod + pop zero // result + pop rg3 // remainder + popa 3 + + sub rg1, rg3, rg2 + addi rg2, 80, rg1 + + // _end saves the display state + jmp _end + +// ------------------------------------------ +// resets the cursor position on the screen to 0x20000. (0,0) +reset: + push bpr + mov spr, bpr + ldw display, rg1 + jmp _end + +// ------------------------------------------ +// clears the screen +clear: + push bpr + mov spr, bpr + // display size = 2000 bytes / 500 words + lli 500 rg0 + ldw display, rg1 + +_clear_loop: + dec rg0 + stw zero, rg1 + addi rg1, 4 + cmp rg0, zero + jgt _clear_loop + jmp _end + +// ------------------------------------------ +// return +_end: + stw rg1, current + + mov bpr, spr + pop bpr + return diff --git a/resources/dsa/lib/maths/core.dsa b/resources/dsa/lib/maths/core.dsa new file mode 100644 index 0000000..217c5dc --- /dev/null +++ b/resources/dsa/lib/maths/core.dsa @@ -0,0 +1,60 @@ +// multiply.dsa +// usage: +// +// include multiply "" +// +// usage for multiply: +// push (arg1) +// push (arg0) +// call multiply::multiply +// pop (arg0) +// pop (arg1) + +multiply: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // load op 2 + ldw bpr, rg1, 12 // load op 1 + +_multiply_loop: + add rg2, rg0, rg2 + dec rg1 + + cmp rg1, zero + jgt _multiply_loop + +_multiply_end: + stw rg2, bpr, 8 + + mov bpr, spr + pop bpr + return + +divmod: + push bpr + mov spr, bpr + + ldw bpr, rg1, 8 // load op 2 + ldw bpr, rg0, 12 // load op 1 + + lli 0, rg3 + +_divmod_loop: + cmp rg0, rg1 + jlt _divmod_end + + sub rg0, rg1, rg0 + inc rg3 + + jmp _divmod_loop + +_divmod_end: + // store div in first arg + // store mod in second arg + stw rg3, bpr, 8 + stw rg0, bpr, 12 + + mov bpr, spr + pop bpr + return diff --git a/resources/dsa/lib/maths/fib.dsa b/resources/dsa/lib/maths/fib.dsa new file mode 100644 index 0000000..c6be26f --- /dev/null +++ b/resources/dsa/lib/maths/fib.dsa @@ -0,0 +1,31 @@ +include print "../io/print.dsa" + +fib_n: + push bpr + mov spr, bpr + + ldw bpr, rg0, 8 // load arg + mov rg1, rg2 + lwi 1, rg1 + +start: + add rg1, rg2, rg3 + + pusha 4 + push rg1 + call print::print_hex_byte + call print::print_newline + pop zero + popa 4 + + mov rg2, rg1 + mov rg3, rg2 + + dec rg0 + cmp rg0, zero + jgt start + + stw rg1, bpr, 8 + mov bpr, spr + pop bpr + return diff --git a/resources/dsa/main.dsa b/resources/dsa/main.dsa new file mode 100644 index 0000000..552c80f --- /dev/null +++ b/resources/dsa/main.dsa @@ -0,0 +1,80 @@ +include fib: "./lib/maths/fib.dsa" +include maths: "./lib/maths/core.dsa" +include print: "./lib/io/print.dsa" + +dw idt: 0xFFFF0000 +dw stack: 0x10000 +init: + // setup interrupt handlers + ldw idt, idr + lwi handle_hard_fault, rg0 + stw rg0, idr, 4 + // set up a stack. + ldw stack, bpr + mov bpr, spr + +dw string: "hello world" +start: + + lwi 37, rg0 + lwi 12, rg1 + push rg0 + push rg1 + call maths::divmod + pop rg0 // result + pop rg1 // remainder + + push rg1 + push rg0 + call print::print_hex_byte + call print::print_whitespace + pop zero + call print::print_hex_byte + call print::print_newline + + lwi string, rg0 + //lwi 10, rg0 + pusha 4 + push rg0 + call print::print + //call fib::fib_n + pop zero + call print::print_newline + popa 4 + + pusha 4 + push rg0 + call print::print + //call fib::fib_n + pop zero + call print::print_newline + popa 4 + + pusha 4 + push rg0 + call print::print + //call fib::fib_n + pop zero + call print::print_newline + popa 4 + + pusha 4 + push rg0 + call print::print + //call fib::fib_n + pop zero + call print::print_newline + popa 4 + + hlt + +// fault handler in case we fail DSA. +dw hard_fault_err: "FATAL: Illegal Instruction or Memory Access!" +handle_hard_fault: + call print::clear + call print::reset + lwi hard_fault_err, rg0 + push rg0 + call print::print + pop zero + hlt \ No newline at end of file diff --git a/resources/dsa/testprint.dsa b/resources/dsa/testprint.dsa new file mode 100644 index 0000000..4542823 --- /dev/null +++ b/resources/dsa/testprint.dsa @@ -0,0 +1,80 @@ +include print "./lib/io/print.dsa" + +dw idt: 0xFFFF0000 +dw stack: 0x10000 +init: + // setup interrupt handlers + ldw idt, idr + lwi handle_hard_fault, rg0 + stw rg0, idr, 4 + // set up a stack. + ldw stack, bpr + mov bpr, spr + + +db string: "I won, the game!" +db hexbyte: 0xab +dw hexword: 0x1234abcd +db replace: "I lost" + +start: + // test print string + lwi string, rg0 + push rg0 + call print::print + pop zero + + // test print hex byte. + ldb hexbyte, rg0 + push rg0 + call print::print_hex_byte + pop zero + + // test print hex word. + ldw hexword, rg0 + push rg0 + call print::print_hex_word + pop zero + + // test print char + lli 0x40, rg0 // print @ + push rg0 + call print::print_byte + pop zero + + // test newline + call print::print_newline + + lwi string rg0 + push rg0 + call print::print + + // test print word + lwi 0x31323334, rg0 // print 1234 + push rg0 + call print::print_word + pop zero + + // test reset cursor pos + call print::reset + + // test print string at reset pos + lwi replace, rg0 + push rg0 + call print::print + pop zero + + hlt + + + +// fault handler in case we fail DSA. +dw hard_fault_err: "FATAL: Illegal Instruction or Memory Access!" +handle_hard_fault: + call print::clear + call print::reset + lwi hard_fault_err, rg0 + push rg0 + call print::print + pop zero + hlt diff --git a/resources/dsb/bf.dsb b/resources/dsb/bf.dsb new file mode 100644 index 0000000..2b831a1 Binary files /dev/null and b/resources/dsb/bf.dsb differ diff --git a/resources/dsb/test.dsb b/resources/dsb/test.dsb new file mode 100644 index 0000000..2b831a1 Binary files /dev/null and b/resources/dsb/test.dsb differ diff --git a/resources/emulator/AndroidManifest.xml b/resources/emulator/AndroidManifest.xml new file mode 100644 index 0000000..21292bf --- /dev/null +++ b/resources/emulator/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/emulator/AppIcon.png b/resources/emulator/AppIcon.png new file mode 100644 index 0000000..4f58b16 Binary files /dev/null and b/resources/emulator/AppIcon.png differ diff --git a/resources/emulator/build_android.ps1 b/resources/emulator/build_android.ps1 new file mode 100644 index 0000000..0a6df1c --- /dev/null +++ b/resources/emulator/build_android.ps1 @@ -0,0 +1,74 @@ +#!/usr/bin/env pwsh + +$env:ANDROID_HOME = "C:\Users\jacob\AppData\Local\Android\Sdk" +$TOOL_PREFIX = "$env:ANDROID_HOME\build-tools\33.0.0" + +# Only really works on Windows, for aarch64. + +# Create directories +New-Item -ItemType Directory -Force -Path "..\target\apk_build\lib\arm64-v8a" +New-Item -ItemType Directory -Force -Path "..\target\apk_build\res\values" +New-Item -ItemType Directory -Force -Path "..\target\apk_build\res\mipmap-hdpi" +New-Item -ItemType Directory -Force -Path "..\target\apk_build\res\mipmap-mdpi" +New-Item -ItemType Directory -Force -Path "..\target\apk_build\res\mipmap-xhdpi" +New-Item -ItemType Directory -Force -Path "..\target\apk_build\res\mipmap-xxhdpi" + +# Copy the shared library +Copy-Item "..\target\aarch64-linux-android\release\libdsa_rs.so" "..\target\apk_build\lib\arm64-v8a\" + +# Copy the manifest +Copy-Item "..\resources\emulator\AndroidManifest.xml" "..\target\apk_build\AndroidManifest.xml" + +# Copy the icons +Copy-Item "..\resources\emulator\AppIcon.png" "..\target\apk_build\res\mipmap-hdpi\ic_launcher.png" +Copy-Item "..\resources\emulator\AppIcon.png" "..\target\apk_build\res\mipmap-mdpi\ic_launcher.png" +Copy-Item "..\resources\emulator\AppIcon.png" "..\target\apk_build\res\mipmap-xhdpi\ic_launcher.png" +Copy-Item "..\resources\emulator\AppIcon.png" "..\target\apk_build\res\mipmap-xxhdpi\ic_launcher.png" + +# Create strings.xml +@" + + + DSA Emulator + +"@ | Out-File -FilePath "..\target\apk_build\res\values\strings.xml" -Encoding utf8 + +# Change to build directory +Push-Location "..\target\apk_build" + +try { + # Compile resources + & "$TOOL_PREFIX\aapt2.exe" compile --dir res -o compiled_resources.zip + + # Link resources + & "$TOOL_PREFIX\aapt2.exe" link -o unaligned.apk ` + -I "$env:ANDROID_HOME\platforms\android-35\android.jar" ` + --manifest AndroidManifest.xml ` + compiled_resources.zip + + # Add native libraries to APK + & "C:\Program Files\7-Zip\7z.exe" a -tzip unaligned.apk lib\* + + # Align APK + & "$TOOL_PREFIX\zipalign.exe" -v 4 unaligned.apk aligned.apk + + # Generate debug keystore if it doesn't exist + if (-not (Test-Path "debug.keystore")) { + & keytool -genkey -v -keystore debug.keystore -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 -storepass android -keypass android -dname "CN=Android Debug,O=Android,C=US" + } + + # Sign APK + & "$TOOL_PREFIX\apksigner.bat" sign --ks debug.keystore --ks-key-alias androiddebugkey --ks-pass pass:android --key-pass pass:android --out dsa_emulator.apk aligned.apk + + # Copy final APK + Copy-Item "dsa_emulator.apk" "..\dsa_emulator.apk" + + Write-Host "APK created successfully at: ..\target\dsa_emulator.apk" -ForegroundColor Green +} +catch { + Write-Error "Build failed: $_" +} +finally { + # Return to original directory + Pop-Location +} diff --git a/resources/emulator/build_android.sh b/resources/emulator/build_android.sh new file mode 100644 index 0000000..fba7943 --- /dev/null +++ b/resources/emulator/build_android.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +export ANDROID_HOME="/mnt/c/Users/jacob/AppData/Local/Android/Sdk" +export TOOL_PREFIX="$ANDROID_HOME/build-tools/35.0.1" + +# Only really works on Linux, for aarch64. + +mkdir -p ../target/apk_build/lib/arm64-v8a +mkdir -p ../target/apk_build/res/values +mkdir -p ../target/apk_build/res/mipmap-hdpi +mkdir -p ../target/apk_build/res/mipmap-mdpi +mkdir -p ../target/apk_build/res/mipmap-xhdpi +mkdir -p ../target/apk_build/res/mipmap-xxhdpi + +# Copy the shared library. +cp ../target/aarch64-linux-android/release/libdsa_rs.so ../target/apk_build/lib/arm64-v8a/ + +# Copy the manifest. +cp AndroidManifest.xml ../target/apk_build/AndroidManifest.xml + +cat << EOF > ../target/apk_build/res/values/strings.xml + + + DSA Emulator + +EOF + +pushd ../target/apk_build + +$TOOL_PREFIX/aapt2 compile --dir res -o compiled_resources.zip +$TOOL_PREFIX/aapt2 link -o unaligned.apk \ + -I "$ANDROID_HOME/platforms/android-35/android.jar" \ + --manifest AndroidManifest.xml \ + compiled_resources.zip + +zip -r unaligned.apk lib/ + +$TOOL_PREFIX/zipalign -v 4 unaligned.apk aligned.apk + +keytool -genkey -v -keystore debug.keystore -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 -storepass android -keypass android -dname "CN=Android Debug,O=Android,C=US" + +$TOOL_PREFIX/apksigner sign --ks debug.keystore --ks-key-alias androiddebugkey --ks-pass pass:android --key-pass pass:android --out dsa_emulator.apk aligned.apk + +cp dsa_emulator.apk ../dsa_emulator.apk + +popd diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5f3e652 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,53 @@ +use std::{ + sync::{Arc, Mutex}, + thread, +}; + +use dsa_rs::emulator::{ + system::{emulator::run_emulator, memory::MainStore, processor::Processor}, + ui::{ + control_unit::ControlPanel, interface::EmulatorUI, memory_inspector::MemoryInspector, + stack_inspector::StackInspector, + }, +}; + +fn main() -> Result<(), eframe::Error> { + // Initialize Channels + let (cmd_sender, cmd_receiver) = std::sync::mpsc::channel(); + let (state_sender, state_receiver) = std::sync::mpsc::channel(); + + let mainstore = MainStore::new(); + let processor = Processor::new(Box::new(mainstore), vec![]); + + thread::spawn(move || { + run_emulator(&cmd_receiver, &state_sender, processor); + }); + + // Create UI + let mut ui = EmulatorUI::new(cmd_sender.clone(), state_receiver); + + // Create UI modules + let control_unit = ControlPanel::new(cmd_sender.clone()); + ui.add_component(Box::new(control_unit)); + + let mem_inspector = MemoryInspector::new(cmd_sender.clone()); + ui.add_component(Box::new(mem_inspector)); + + let stack_inspector = StackInspector::new(); + ui.add_component(Box::new(stack_inspector)); + + // Run UI + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default().with_inner_size([800.0, 600.0]), + ..Default::default() + }; + + eframe::run_native( + "DSA Simulator (Damn Simple Architecture 🔥)", + options, + Box::new(move |cc| { + cc.egui_ctx.set_visuals(egui::Visuals::default()); + Ok(Box::new(ui)) + }), + ) +}