├── .cargo └── config.toml ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── release.yml ├── .gitignore ├── AUTHORS ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── README.md ├── docs ├── .gitignore ├── 404.md ├── Gemfile ├── Gemfile.lock ├── _config.yml ├── editor.md ├── favicon.ico ├── introduction.md ├── primitives.md └── tutorial.md ├── rust-toolchain.toml ├── rustfmt.toml └── src ├── editor.rs ├── interactive.rs ├── lib.rs ├── main.rs └── view.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-unknown-linux-gnu] 2 | linker = "/usr/bin/clang" 3 | rustflags = ["-Clink-arg=-fuse-ld=lld", "-Zshare-generics=y"] 4 | 5 | [target.x86_64-apple-darwin] 6 | rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/zld", "-Zshare-generics=y"] 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | AUTHORS merge=union -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: cadenhaustein 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [created] 4 | 5 | jobs: 6 | release: 7 | strategy: 8 | fail-fast: true 9 | matrix: 10 | include: 11 | - os: ubuntu-latest 12 | target: x86_64-pc-windows-gnu 13 | archive-format: zip 14 | - os: ubuntu-latest 15 | target: x86_64-unknown-linux-musl 16 | archive-format: tar.gz tar.xz 17 | - os: macos-latest 18 | target: x86_64-apple-darwin 19 | archive-format: zip 20 | name: Release for ${{ matrix.target }} 21 | runs-on: ${{ matrix.os }} 22 | steps: 23 | - uses: actions/checkout@master 24 | - uses: actions/cache@v2 25 | with: 26 | path: | 27 | ~/.cargo/registry 28 | ~/.cargo/git 29 | target 30 | key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 31 | - run: brew install michaeleisel/zld/zld 32 | if: matrix.os == 'macos-latest' 33 | - name: Compile and release 34 | uses: rust-build/rust-build.action@latest 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | RUSTTARGET: ${{ matrix.target }} 38 | ARCHIVE_TYPES: ${{ matrix.archive-format }} 39 | EXTA_FILES: 'LICENSE.md README.md' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | debug/ 2 | target/ 3 | 4 | **/*.rs.bk 5 | 6 | .pdb 7 | mlatu.erl 8 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # The AUTHORS Certificate 2 | # First edition, Fourteenth draft 3 | # 4 | # By proposing a change to this project that adds a line like 5 | # 6 | # Name (URL) [Working For] 7 | # 8 | # below, you certify: 9 | # 10 | # 1. All of your contributions to this project are and will be your own, 11 | # original work, licensed on the same terms as this project. 12 | # 13 | # 2. If someone else might own intellectual property in your 14 | # contributions, like an employer or client, you've added their legal 15 | # name in square brackets and got their written permission to submit 16 | # your contribution. 17 | # 18 | # 3. If you haven't added a name in square brackets, you are sure that 19 | # you have the legal right to license all your contributions so far 20 | # by yourself. 21 | # 22 | # 4. If you make any future contribution under different intellectual 23 | # property circumstances, you'll propose a change to add another line 24 | # to this file for that contribution. 25 | # 26 | # 5. The name, e-mail, and URL you've added to this file are yours. You 27 | # understand the project will make this file public. 28 | 29 | Caden Haustein 30 | Garklein 31 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Thank you 2 | 3 | Thank you for contributing to mlatu! It is very important to us that the community is involved in the codebase and changes made; we don't want to fall into a "Benevolent Dictator for Life" model. 4 | 5 | ## Introduction and Overview 6 | 7 | Pull requests are very welcome. For major changes, please open an issue first to discuss what you would like to change. You can also join the Discord server listed on the README for help setting up and making changes, as well as asking questions about the codebase. 8 | 9 | ## Setup 10 | 11 | To create a local copy of mlatu with all necessary dependencies, you will need to install Rust and Erlang. To install Rust, follow the instructions at . To install Erlang, you can find a download at . 12 | 13 | To contribute to mlatu, it's easiest to install the GitHub CLI tool (instructions are at ). After installation, authenticate yourself by running `gh auth login`. Then, fork and download a local copy of the repository by running `gh repo fork mlatu-lang/mlatu`. 14 | 15 | ## Making changes 16 | 17 | Now, make any changes you wish to include in the PR. Make sure to run `cargo fmt` and `cargo clippy` to make sure formatting is correct and there are no lint warnings. To commit your changes, run `git add *` and `git commit -m "Commit message"`. Don't worry too much about the commit message being descriptive, as we will squash the commits before we merge. Then run `gh repo sync` to sync with the online version. 18 | 19 | ## Creating a pull request 20 | 21 | Once you are satisfied with your changes, create a pull request by running `gh pr create`. This will prompt you for the title and the body of the pull request. Please make the title short and descriptive of the changes you've made. The description can be longer and should note any issues this pull request should close, any remaining issues, and anything the mlatu-lang team should know. This will output a URL where you can view the changes you've made and any comments made on that pull request. 22 | 23 | Once the pull request is created, one of the mlatu-lang team members will come take a look at your changes and approve or request changes. If the pull request is approved, it will be merged into `main`. That's success! If there are changes requested, we'd ask that you do your best to fix them (asking clarifying questions or for help if you need it is great), and then we will review again. 24 | 25 | If you are a new contributor, we ask that you would read over the `AUTHORS` file and add your own name and email to the file, in the same pull request. 26 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.7.6" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 10 | dependencies = [ 11 | "getrandom", 12 | "once_cell", 13 | "version_check", 14 | ] 15 | 16 | [[package]] 17 | name = "atty" 18 | version = "0.2.14" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 21 | dependencies = [ 22 | "hermit-abi", 23 | "libc", 24 | "winapi", 25 | ] 26 | 27 | [[package]] 28 | name = "autocfg" 29 | version = "1.1.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 32 | 33 | [[package]] 34 | name = "bitflags" 35 | version = "1.3.2" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 38 | 39 | [[package]] 40 | name = "bitmaps" 41 | version = "2.1.0" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" 44 | dependencies = [ 45 | "typenum", 46 | ] 47 | 48 | [[package]] 49 | name = "bytes" 50 | version = "1.1.0" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 53 | 54 | [[package]] 55 | name = "cfg-if" 56 | version = "1.0.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 59 | 60 | [[package]] 61 | name = "clap" 62 | version = "3.1.12" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "7c167e37342afc5f33fd87bbc870cedd020d2a6dffa05d45ccd9241fbdd146db" 65 | dependencies = [ 66 | "atty", 67 | "bitflags", 68 | "clap_lex", 69 | "indexmap", 70 | "lazy_static", 71 | "strsim", 72 | "termcolor", 73 | "textwrap", 74 | ] 75 | 76 | [[package]] 77 | name = "clap_lex" 78 | version = "0.1.1" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "189ddd3b5d32a70b35e7686054371742a937b0d99128e76dde6340210e966669" 81 | dependencies = [ 82 | "os_str_bytes", 83 | ] 84 | 85 | [[package]] 86 | name = "combine" 87 | version = "4.6.3" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062" 90 | dependencies = [ 91 | "bytes", 92 | "memchr", 93 | ] 94 | 95 | [[package]] 96 | name = "crossterm" 97 | version = "0.23.2" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "a2102ea4f781910f8a5b98dd061f4c2023f479ce7bb1236330099ceb5a93cf17" 100 | dependencies = [ 101 | "bitflags", 102 | "crossterm_winapi", 103 | "futures-core", 104 | "libc", 105 | "mio", 106 | "parking_lot", 107 | "signal-hook", 108 | "signal-hook-mio", 109 | "winapi", 110 | ] 111 | 112 | [[package]] 113 | name = "crossterm_winapi" 114 | version = "0.9.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" 117 | dependencies = [ 118 | "winapi", 119 | ] 120 | 121 | [[package]] 122 | name = "dashmap" 123 | version = "4.0.2" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" 126 | dependencies = [ 127 | "cfg-if", 128 | "num_cpus", 129 | ] 130 | 131 | [[package]] 132 | name = "futures-core" 133 | version = "0.3.21" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 136 | 137 | [[package]] 138 | name = "getrandom" 139 | version = "0.2.6" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" 142 | dependencies = [ 143 | "cfg-if", 144 | "libc", 145 | "wasi 0.10.2+wasi-snapshot-preview1", 146 | ] 147 | 148 | [[package]] 149 | name = "hashbrown" 150 | version = "0.11.2" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 153 | dependencies = [ 154 | "ahash", 155 | ] 156 | 157 | [[package]] 158 | name = "hermit-abi" 159 | version = "0.1.19" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 162 | dependencies = [ 163 | "libc", 164 | ] 165 | 166 | [[package]] 167 | name = "im" 168 | version = "15.1.0" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" 171 | dependencies = [ 172 | "bitmaps", 173 | "rand_core", 174 | "rand_xoshiro", 175 | "sized-chunks", 176 | "typenum", 177 | "version_check", 178 | ] 179 | 180 | [[package]] 181 | name = "indexmap" 182 | version = "1.8.1" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" 185 | dependencies = [ 186 | "autocfg", 187 | "hashbrown", 188 | ] 189 | 190 | [[package]] 191 | name = "lasso" 192 | version = "0.6.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "aeb7b21a526375c5ca55f1a6dfd4e1fad9fa4edd750f530252a718a44b2608f0" 195 | dependencies = [ 196 | "dashmap", 197 | "hashbrown", 198 | ] 199 | 200 | [[package]] 201 | name = "lazy_static" 202 | version = "1.4.0" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 205 | 206 | [[package]] 207 | name = "libc" 208 | version = "0.2.153" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 211 | 212 | [[package]] 213 | name = "lock_api" 214 | version = "0.4.7" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 217 | dependencies = [ 218 | "autocfg", 219 | "scopeguard", 220 | ] 221 | 222 | [[package]] 223 | name = "log" 224 | version = "0.4.16" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" 227 | dependencies = [ 228 | "cfg-if", 229 | ] 230 | 231 | [[package]] 232 | name = "matches" 233 | version = "0.1.9" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 236 | 237 | [[package]] 238 | name = "memchr" 239 | version = "2.4.1" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 242 | 243 | [[package]] 244 | name = "mio" 245 | version = "0.8.11" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 248 | dependencies = [ 249 | "libc", 250 | "log", 251 | "wasi 0.11.0+wasi-snapshot-preview1", 252 | "windows-sys 0.48.0", 253 | ] 254 | 255 | [[package]] 256 | name = "mlatu" 257 | version = "0.1.0" 258 | dependencies = [ 259 | "clap", 260 | "combine", 261 | "crossterm", 262 | "im", 263 | "mlatu 0.1.0 (git+https://github.com/mlatu-lang/libraries)", 264 | "tokio", 265 | "tokio-stream", 266 | "unic-ucd-category", 267 | ] 268 | 269 | [[package]] 270 | name = "mlatu" 271 | version = "0.1.0" 272 | source = "git+https://github.com/mlatu-lang/libraries#8cef57f9522b29162f4fdb6568187e7d7cf72293" 273 | dependencies = [ 274 | "im", 275 | "lasso", 276 | "typed-arena", 277 | ] 278 | 279 | [[package]] 280 | name = "num_cpus" 281 | version = "1.13.1" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 284 | dependencies = [ 285 | "hermit-abi", 286 | "libc", 287 | ] 288 | 289 | [[package]] 290 | name = "once_cell" 291 | version = "1.10.0" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" 294 | 295 | [[package]] 296 | name = "os_str_bytes" 297 | version = "6.0.0" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 300 | 301 | [[package]] 302 | name = "parking_lot" 303 | version = "0.12.0" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" 306 | dependencies = [ 307 | "lock_api", 308 | "parking_lot_core", 309 | ] 310 | 311 | [[package]] 312 | name = "parking_lot_core" 313 | version = "0.9.2" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37" 316 | dependencies = [ 317 | "cfg-if", 318 | "libc", 319 | "redox_syscall", 320 | "smallvec", 321 | "windows-sys 0.34.0", 322 | ] 323 | 324 | [[package]] 325 | name = "pin-project-lite" 326 | version = "0.2.8" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" 329 | 330 | [[package]] 331 | name = "proc-macro2" 332 | version = "1.0.37" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" 335 | dependencies = [ 336 | "unicode-xid", 337 | ] 338 | 339 | [[package]] 340 | name = "quote" 341 | version = "1.0.18" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" 344 | dependencies = [ 345 | "proc-macro2", 346 | ] 347 | 348 | [[package]] 349 | name = "rand_core" 350 | version = "0.6.3" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 353 | 354 | [[package]] 355 | name = "rand_xoshiro" 356 | version = "0.6.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" 359 | dependencies = [ 360 | "rand_core", 361 | ] 362 | 363 | [[package]] 364 | name = "redox_syscall" 365 | version = "0.2.13" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" 368 | dependencies = [ 369 | "bitflags", 370 | ] 371 | 372 | [[package]] 373 | name = "scopeguard" 374 | version = "1.1.0" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 377 | 378 | [[package]] 379 | name = "signal-hook" 380 | version = "0.3.13" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d" 383 | dependencies = [ 384 | "libc", 385 | "signal-hook-registry", 386 | ] 387 | 388 | [[package]] 389 | name = "signal-hook-mio" 390 | version = "0.2.3" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" 393 | dependencies = [ 394 | "libc", 395 | "mio", 396 | "signal-hook", 397 | ] 398 | 399 | [[package]] 400 | name = "signal-hook-registry" 401 | version = "1.4.0" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 404 | dependencies = [ 405 | "libc", 406 | ] 407 | 408 | [[package]] 409 | name = "sized-chunks" 410 | version = "0.6.5" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" 413 | dependencies = [ 414 | "bitmaps", 415 | "typenum", 416 | ] 417 | 418 | [[package]] 419 | name = "smallvec" 420 | version = "1.8.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 423 | 424 | [[package]] 425 | name = "strsim" 426 | version = "0.10.0" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 429 | 430 | [[package]] 431 | name = "syn" 432 | version = "1.0.91" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" 435 | dependencies = [ 436 | "proc-macro2", 437 | "quote", 438 | "unicode-xid", 439 | ] 440 | 441 | [[package]] 442 | name = "termcolor" 443 | version = "1.1.3" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 446 | dependencies = [ 447 | "winapi-util", 448 | ] 449 | 450 | [[package]] 451 | name = "textwrap" 452 | version = "0.15.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 455 | 456 | [[package]] 457 | name = "tokio" 458 | version = "1.18.5" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "0e050c618355082ae5a89ec63bbf897225d5ffe84c7c4e036874e4d185a5044e" 461 | dependencies = [ 462 | "num_cpus", 463 | "pin-project-lite", 464 | "tokio-macros", 465 | ] 466 | 467 | [[package]] 468 | name = "tokio-macros" 469 | version = "1.7.0" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" 472 | dependencies = [ 473 | "proc-macro2", 474 | "quote", 475 | "syn", 476 | ] 477 | 478 | [[package]] 479 | name = "tokio-stream" 480 | version = "0.1.8" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" 483 | dependencies = [ 484 | "futures-core", 485 | "pin-project-lite", 486 | "tokio", 487 | ] 488 | 489 | [[package]] 490 | name = "typed-arena" 491 | version = "2.0.1" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" 494 | 495 | [[package]] 496 | name = "typenum" 497 | version = "1.15.0" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 500 | 501 | [[package]] 502 | name = "unic-char-property" 503 | version = "0.9.0" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" 506 | dependencies = [ 507 | "unic-char-range", 508 | ] 509 | 510 | [[package]] 511 | name = "unic-char-range" 512 | version = "0.9.0" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" 515 | 516 | [[package]] 517 | name = "unic-common" 518 | version = "0.9.0" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" 521 | 522 | [[package]] 523 | name = "unic-ucd-category" 524 | version = "0.9.0" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "1b8d4591f5fcfe1bd4453baaf803c40e1b1e69ff8455c47620440b46efef91c0" 527 | dependencies = [ 528 | "matches", 529 | "unic-char-property", 530 | "unic-char-range", 531 | "unic-ucd-version", 532 | ] 533 | 534 | [[package]] 535 | name = "unic-ucd-version" 536 | version = "0.9.0" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" 539 | dependencies = [ 540 | "unic-common", 541 | ] 542 | 543 | [[package]] 544 | name = "unicode-xid" 545 | version = "0.2.2" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 548 | 549 | [[package]] 550 | name = "version_check" 551 | version = "0.9.4" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 554 | 555 | [[package]] 556 | name = "wasi" 557 | version = "0.10.2+wasi-snapshot-preview1" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 560 | 561 | [[package]] 562 | name = "wasi" 563 | version = "0.11.0+wasi-snapshot-preview1" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 566 | 567 | [[package]] 568 | name = "winapi" 569 | version = "0.3.9" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 572 | dependencies = [ 573 | "winapi-i686-pc-windows-gnu", 574 | "winapi-x86_64-pc-windows-gnu", 575 | ] 576 | 577 | [[package]] 578 | name = "winapi-i686-pc-windows-gnu" 579 | version = "0.4.0" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 582 | 583 | [[package]] 584 | name = "winapi-util" 585 | version = "0.1.5" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 588 | dependencies = [ 589 | "winapi", 590 | ] 591 | 592 | [[package]] 593 | name = "winapi-x86_64-pc-windows-gnu" 594 | version = "0.4.0" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 597 | 598 | [[package]] 599 | name = "windows-sys" 600 | version = "0.34.0" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" 603 | dependencies = [ 604 | "windows_aarch64_msvc 0.34.0", 605 | "windows_i686_gnu 0.34.0", 606 | "windows_i686_msvc 0.34.0", 607 | "windows_x86_64_gnu 0.34.0", 608 | "windows_x86_64_msvc 0.34.0", 609 | ] 610 | 611 | [[package]] 612 | name = "windows-sys" 613 | version = "0.48.0" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 616 | dependencies = [ 617 | "windows-targets", 618 | ] 619 | 620 | [[package]] 621 | name = "windows-targets" 622 | version = "0.48.5" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 625 | dependencies = [ 626 | "windows_aarch64_gnullvm", 627 | "windows_aarch64_msvc 0.48.5", 628 | "windows_i686_gnu 0.48.5", 629 | "windows_i686_msvc 0.48.5", 630 | "windows_x86_64_gnu 0.48.5", 631 | "windows_x86_64_gnullvm", 632 | "windows_x86_64_msvc 0.48.5", 633 | ] 634 | 635 | [[package]] 636 | name = "windows_aarch64_gnullvm" 637 | version = "0.48.5" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 640 | 641 | [[package]] 642 | name = "windows_aarch64_msvc" 643 | version = "0.34.0" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" 646 | 647 | [[package]] 648 | name = "windows_aarch64_msvc" 649 | version = "0.48.5" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 652 | 653 | [[package]] 654 | name = "windows_i686_gnu" 655 | version = "0.34.0" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" 658 | 659 | [[package]] 660 | name = "windows_i686_gnu" 661 | version = "0.48.5" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 664 | 665 | [[package]] 666 | name = "windows_i686_msvc" 667 | version = "0.34.0" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" 670 | 671 | [[package]] 672 | name = "windows_i686_msvc" 673 | version = "0.48.5" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 676 | 677 | [[package]] 678 | name = "windows_x86_64_gnu" 679 | version = "0.34.0" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" 682 | 683 | [[package]] 684 | name = "windows_x86_64_gnu" 685 | version = "0.48.5" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 688 | 689 | [[package]] 690 | name = "windows_x86_64_gnullvm" 691 | version = "0.48.5" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 694 | 695 | [[package]] 696 | name = "windows_x86_64_msvc" 697 | version = "0.34.0" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" 700 | 701 | [[package]] 702 | name = "windows_x86_64_msvc" 703 | version = "0.48.5" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 706 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ["strip"] 2 | 3 | [package] 4 | name = "mlatu" 5 | version = "0.1.0" 6 | edition = "2021" 7 | description = "The mlatu programming language" 8 | repository = "https://github.com/mlatu-lang/mlatu" 9 | readme = "docs/README.md" 10 | keywords = ["cli", "parser", "langdev", "language"] 11 | categories = ["command-line-utilities", "compilers", "text-editors"] 12 | license-file = "https://mlatu-lang.github.io/license/" 13 | include = ["src/**/*", "README.md"] 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | combine = "4.6.3" 19 | crossterm = { version = "0.23.2", features = ["event-stream"] } 20 | unic-ucd-category = "0.9.0" 21 | tokio-stream = "0.1.8" 22 | clap = { version = "3.1.10", features = ["cargo"] } 23 | mlatu-lib = { git = "https://github.com/mlatu-lang/libraries", package="mlatu"} 24 | im = "15.1.0" 25 | tokio = { version = "1.18.5", features = ["rt", "macros", "rt-multi-thread"] } 26 | 27 | [features] 28 | 29 | [profile.release] 30 | strip = true 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mlatu 2 | ===== 3 | 4 | mlatu is a declarative and concatenative programming language. It uses term rewriting as an evalutation model. 5 | 6 | ![Lines of code](https://img.shields.io/tokei/lines/github/mlatu-lang/mlatu) 7 | ![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/mlatu-lang/mlatu?include_prereleases) 8 | ![GitHub last commit](https://img.shields.io/github/last-commit/mlatu-lang/mlatu) 9 | ![Discord](https://img.shields.io/discord/889248218460852235) 10 | 11 | Table of Contents 12 | ----------------- 13 | 14 | - [mlatu](#mlatu) 15 | - [Table of Contents](#table-of-contents) 16 | - [Introduction](#introduction) 17 | - [Installation](#installation) 18 | - [Usage](#usage) 19 | - [Known issues and limitations](#known-issues-and-limitations) 20 | - [Getting help](#getting-help) 21 | - [Contributing](#contributing) 22 | - [Acknowledgements](#acknowledgements) 23 | - [License](#license) 24 | 25 | Introduction 26 | ------------ 27 | 28 | mlatu is a concatenative programming language; you can concatenate two programs and the result will be the concatenation of the programs' individual results. It is declarative: instead of telling the implementation what to *do*, one tells the implementation what *is*. mlatu is evaluated via a term-rewriting system and so all expressions are referentially transparent, evaluating to the same result each time they are written. 29 | 30 | Installation 31 | ------------ 32 | 33 | First, you need to have Rust and Erlang (and Clang on Windows) installed on your system and in your `PATH`. To install Rust, follow the instructions at . To install Erlang, you can find a download at . To install Clang on Windows, you can find a download link at (make sure you set `LIBCLANG_PATH` environment variable to the `bin` directory of that download). 34 | 35 | ```bash 36 | git clone https://github.com/mlatu-lang/mlatu 37 | cd mlatu 38 | cargo install --path . 39 | ``` 40 | 41 | Usage 42 | ----- 43 | 44 | Running `mlatu` will start up an interactive TUI with structured input. The input is entered and manipulated on the left side, and the rewritten form will appear on the right side. where toplevel terms can be typed and their respective reductions will be printed out. If any arguments are given, they are interpreted as files containing additional rewrite-rules to load. `ESC` to close the TUI. 45 | 46 | Running `mlatu edit ` will open a structured editor for the rules contained in that file. You will be able to see and manipulate both the left (pattern) and right (replacement) sides. You can also navigate between rules and manipulate the rules just as you would terms. `CTRL-W` to save and `ESC` to close the TUI. 47 | 48 | Known issues and limitations 49 | ---------------------------- 50 | 51 | Currently, there is no way of doing I/O (input and output). This is a top priority and will be resolved as soon as a suitable model for interacting with the rewriting system is chosen. 52 | 53 | Another issue is the lack of true patterns and a type system. Several solutions to this issue are currently being investigated, we would love any ideas or feedback on the Discord server. 54 | 55 | Getting help 56 | ------------ 57 | 58 | There are a couple ways to get help if you have questions, thoughts, or issues. The GitHub issue tracker () is the best place to submit issues or bug reports. If you have a thought, suggestion, question, or just want to chat about mlatu, there is a small Discord server at . 59 | 60 | Contributing 61 | ------------ 62 | 63 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 64 | 65 | See [this guide](/CONTRIBUTING.md) for more specific and detailed development workflows. 66 | 67 | Acknowledgements 68 | ---------------- 69 | 70 | mlatu is inspired by a number of pre-existing concatenative and term-rewriting languages. These include but are not limited to, Kitten, Cat, Factor, Forth, min, Joy, TXL, META II, Pure, Clean, Refal, and Prolog. The structured editor is heavily inspired by Sapling. 71 | 72 | License 73 | ------- 74 | 75 | mlatu, like all mlatu-lang projects, is licensed under https://mlatu-lang.github.io/license/ 76 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .sass-cache 3 | .jekyll-cache 4 | .jekyll-metadata 5 | vendor 6 | -------------------------------------------------------------------------------- /docs/404.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 404 4 | nav_exclude: true 5 | permalink: ./404.html 6 | --- 7 | 8 | Sorry, the page you requested couldn't be found. -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | # Hello! This is where you manage which Jekyll version is used to run. 3 | # When you want to use a different version, change it below, save the 4 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: 5 | # 6 | # bundle exec jekyll serve 7 | # 8 | # This will help ensure the proper Jekyll version is running. 9 | # Happy Jekylling! 10 | # gem "jekyll", "~> 4.2.0" 11 | # This is the default theme for new Jekyll sites. You may change this to anything you like. 12 | gem "minima", "~> 2.5" 13 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and 14 | # uncomment the line below. To upgrade, run `bundle update github-pages`. 15 | gem "github-pages", "~> 219", group: :jekyll_plugins 16 | # If you have any plugins, put them here! 17 | group :jekyll_plugins do 18 | gem "jekyll-feed", "~> 0.12" 19 | end 20 | 21 | # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem 22 | # and associated library. 23 | platforms :mingw, :x64_mingw, :mswin, :jruby do 24 | gem "tzinfo", "~> 1.2" 25 | gem "tzinfo-data" 26 | end 27 | 28 | # Performance-booster for watching directories on Windows 29 | gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] 30 | 31 | gem "jekyll-redirect-from" -------------------------------------------------------------------------------- /docs/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (6.0.6.1) 5 | concurrent-ruby (~> 1.0, >= 1.0.2) 6 | i18n (>= 0.7, < 2) 7 | minitest (~> 5.1) 8 | tzinfo (~> 1.1) 9 | zeitwerk (~> 2.2, >= 2.2.2) 10 | addressable (2.8.0) 11 | public_suffix (>= 2.0.2, < 5.0) 12 | coffee-script (2.4.1) 13 | coffee-script-source 14 | execjs 15 | coffee-script-source (1.11.1) 16 | colorator (1.1.0) 17 | commonmarker (0.17.13) 18 | ruby-enum (~> 0.5) 19 | concurrent-ruby (1.2.0) 20 | dnsruby (1.61.7) 21 | simpleidn (~> 0.1) 22 | em-websocket (0.5.2) 23 | eventmachine (>= 0.12.9) 24 | http_parser.rb (~> 0.6.0) 25 | ethon (0.14.0) 26 | ffi (>= 1.15.0) 27 | eventmachine (1.2.7) 28 | execjs (2.8.1) 29 | faraday (1.8.0) 30 | faraday-em_http (~> 1.0) 31 | faraday-em_synchrony (~> 1.0) 32 | faraday-excon (~> 1.1) 33 | faraday-httpclient (~> 1.0.1) 34 | faraday-net_http (~> 1.0) 35 | faraday-net_http_persistent (~> 1.1) 36 | faraday-patron (~> 1.0) 37 | faraday-rack (~> 1.0) 38 | multipart-post (>= 1.2, < 3) 39 | ruby2_keywords (>= 0.0.4) 40 | faraday-em_http (1.0.0) 41 | faraday-em_synchrony (1.0.0) 42 | faraday-excon (1.1.0) 43 | faraday-httpclient (1.0.1) 44 | faraday-net_http (1.0.1) 45 | faraday-net_http_persistent (1.2.0) 46 | faraday-patron (1.0.0) 47 | faraday-rack (1.0.0) 48 | ffi (1.15.4) 49 | forwardable-extended (2.6.0) 50 | gemoji (3.0.1) 51 | github-pages (219) 52 | github-pages-health-check (= 1.17.7) 53 | jekyll (= 3.9.0) 54 | jekyll-avatar (= 0.7.0) 55 | jekyll-coffeescript (= 1.1.1) 56 | jekyll-commonmark-ghpages (= 0.1.6) 57 | jekyll-default-layout (= 0.1.4) 58 | jekyll-feed (= 0.15.1) 59 | jekyll-gist (= 1.5.0) 60 | jekyll-github-metadata (= 2.13.0) 61 | jekyll-mentions (= 1.6.0) 62 | jekyll-optional-front-matter (= 0.3.2) 63 | jekyll-paginate (= 1.1.0) 64 | jekyll-readme-index (= 0.3.0) 65 | jekyll-redirect-from (= 0.16.0) 66 | jekyll-relative-links (= 0.6.1) 67 | jekyll-remote-theme (= 0.4.3) 68 | jekyll-sass-converter (= 1.5.2) 69 | jekyll-seo-tag (= 2.7.1) 70 | jekyll-sitemap (= 1.4.0) 71 | jekyll-swiss (= 1.0.0) 72 | jekyll-theme-architect (= 0.2.0) 73 | jekyll-theme-cayman (= 0.2.0) 74 | jekyll-theme-dinky (= 0.2.0) 75 | jekyll-theme-hacker (= 0.2.0) 76 | jekyll-theme-leap-day (= 0.2.0) 77 | jekyll-theme-merlot (= 0.2.0) 78 | jekyll-theme-midnight (= 0.2.0) 79 | jekyll-theme-minimal (= 0.2.0) 80 | jekyll-theme-modernist (= 0.2.0) 81 | jekyll-theme-primer (= 0.6.0) 82 | jekyll-theme-slate (= 0.2.0) 83 | jekyll-theme-tactile (= 0.2.0) 84 | jekyll-theme-time-machine (= 0.2.0) 85 | jekyll-titles-from-headings (= 0.5.3) 86 | jemoji (= 0.12.0) 87 | kramdown (= 2.3.1) 88 | kramdown-parser-gfm (= 1.1.0) 89 | liquid (= 4.0.3) 90 | mercenary (~> 0.3) 91 | minima (= 2.5.1) 92 | nokogiri (>= 1.10.4, < 2.0) 93 | rouge (= 3.26.0) 94 | terminal-table (~> 1.4) 95 | github-pages-health-check (1.17.7) 96 | addressable (~> 2.3) 97 | dnsruby (~> 1.60) 98 | octokit (~> 4.0) 99 | public_suffix (>= 3.0, < 5.0) 100 | typhoeus (~> 1.3) 101 | html-pipeline (2.14.0) 102 | activesupport (>= 2) 103 | nokogiri (>= 1.4) 104 | http_parser.rb (0.6.0) 105 | i18n (0.9.5) 106 | concurrent-ruby (~> 1.0) 107 | jekyll (3.9.0) 108 | addressable (~> 2.4) 109 | colorator (~> 1.0) 110 | em-websocket (~> 0.5) 111 | i18n (~> 0.7) 112 | jekyll-sass-converter (~> 1.0) 113 | jekyll-watch (~> 2.0) 114 | kramdown (>= 1.17, < 3) 115 | liquid (~> 4.0) 116 | mercenary (~> 0.3.3) 117 | pathutil (~> 0.9) 118 | rouge (>= 1.7, < 4) 119 | safe_yaml (~> 1.0) 120 | jekyll-avatar (0.7.0) 121 | jekyll (>= 3.0, < 5.0) 122 | jekyll-coffeescript (1.1.1) 123 | coffee-script (~> 2.2) 124 | coffee-script-source (~> 1.11.1) 125 | jekyll-commonmark (1.3.1) 126 | commonmarker (~> 0.14) 127 | jekyll (>= 3.7, < 5.0) 128 | jekyll-commonmark-ghpages (0.1.6) 129 | commonmarker (~> 0.17.6) 130 | jekyll-commonmark (~> 1.2) 131 | rouge (>= 2.0, < 4.0) 132 | jekyll-default-layout (0.1.4) 133 | jekyll (~> 3.0) 134 | jekyll-feed (0.15.1) 135 | jekyll (>= 3.7, < 5.0) 136 | jekyll-gist (1.5.0) 137 | octokit (~> 4.2) 138 | jekyll-github-metadata (2.13.0) 139 | jekyll (>= 3.4, < 5.0) 140 | octokit (~> 4.0, != 4.4.0) 141 | jekyll-mentions (1.6.0) 142 | html-pipeline (~> 2.3) 143 | jekyll (>= 3.7, < 5.0) 144 | jekyll-optional-front-matter (0.3.2) 145 | jekyll (>= 3.0, < 5.0) 146 | jekyll-paginate (1.1.0) 147 | jekyll-readme-index (0.3.0) 148 | jekyll (>= 3.0, < 5.0) 149 | jekyll-redirect-from (0.16.0) 150 | jekyll (>= 3.3, < 5.0) 151 | jekyll-relative-links (0.6.1) 152 | jekyll (>= 3.3, < 5.0) 153 | jekyll-remote-theme (0.4.3) 154 | addressable (~> 2.0) 155 | jekyll (>= 3.5, < 5.0) 156 | jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) 157 | rubyzip (>= 1.3.0, < 3.0) 158 | jekyll-sass-converter (1.5.2) 159 | sass (~> 3.4) 160 | jekyll-seo-tag (2.7.1) 161 | jekyll (>= 3.8, < 5.0) 162 | jekyll-sitemap (1.4.0) 163 | jekyll (>= 3.7, < 5.0) 164 | jekyll-swiss (1.0.0) 165 | jekyll-theme-architect (0.2.0) 166 | jekyll (> 3.5, < 5.0) 167 | jekyll-seo-tag (~> 2.0) 168 | jekyll-theme-cayman (0.2.0) 169 | jekyll (> 3.5, < 5.0) 170 | jekyll-seo-tag (~> 2.0) 171 | jekyll-theme-dinky (0.2.0) 172 | jekyll (> 3.5, < 5.0) 173 | jekyll-seo-tag (~> 2.0) 174 | jekyll-theme-hacker (0.2.0) 175 | jekyll (> 3.5, < 5.0) 176 | jekyll-seo-tag (~> 2.0) 177 | jekyll-theme-leap-day (0.2.0) 178 | jekyll (> 3.5, < 5.0) 179 | jekyll-seo-tag (~> 2.0) 180 | jekyll-theme-merlot (0.2.0) 181 | jekyll (> 3.5, < 5.0) 182 | jekyll-seo-tag (~> 2.0) 183 | jekyll-theme-midnight (0.2.0) 184 | jekyll (> 3.5, < 5.0) 185 | jekyll-seo-tag (~> 2.0) 186 | jekyll-theme-minimal (0.2.0) 187 | jekyll (> 3.5, < 5.0) 188 | jekyll-seo-tag (~> 2.0) 189 | jekyll-theme-modernist (0.2.0) 190 | jekyll (> 3.5, < 5.0) 191 | jekyll-seo-tag (~> 2.0) 192 | jekyll-theme-primer (0.6.0) 193 | jekyll (> 3.5, < 5.0) 194 | jekyll-github-metadata (~> 2.9) 195 | jekyll-seo-tag (~> 2.0) 196 | jekyll-theme-slate (0.2.0) 197 | jekyll (> 3.5, < 5.0) 198 | jekyll-seo-tag (~> 2.0) 199 | jekyll-theme-tactile (0.2.0) 200 | jekyll (> 3.5, < 5.0) 201 | jekyll-seo-tag (~> 2.0) 202 | jekyll-theme-time-machine (0.2.0) 203 | jekyll (> 3.5, < 5.0) 204 | jekyll-seo-tag (~> 2.0) 205 | jekyll-titles-from-headings (0.5.3) 206 | jekyll (>= 3.3, < 5.0) 207 | jekyll-watch (2.2.1) 208 | listen (~> 3.0) 209 | jemoji (0.12.0) 210 | gemoji (~> 3.0) 211 | html-pipeline (~> 2.2) 212 | jekyll (>= 3.0, < 5.0) 213 | kramdown (2.3.1) 214 | rexml 215 | kramdown-parser-gfm (1.1.0) 216 | kramdown (~> 2.0) 217 | liquid (4.0.3) 218 | listen (3.7.0) 219 | rb-fsevent (~> 0.10, >= 0.10.3) 220 | rb-inotify (~> 0.9, >= 0.9.10) 221 | mercenary (0.3.6) 222 | minima (2.5.1) 223 | jekyll (>= 3.5, < 5.0) 224 | jekyll-feed (~> 0.9) 225 | jekyll-seo-tag (~> 2.1) 226 | minitest (5.17.0) 227 | multipart-post (2.1.1) 228 | nokogiri (1.16.5-arm64-darwin) 229 | racc (~> 1.4) 230 | nokogiri (1.16.5-x86_64-darwin) 231 | racc (~> 1.4) 232 | nokogiri (1.16.5-x86_64-linux) 233 | racc (~> 1.4) 234 | octokit (4.21.0) 235 | faraday (>= 0.9) 236 | sawyer (~> 0.8.0, >= 0.5.3) 237 | pathutil (0.16.2) 238 | forwardable-extended (~> 2.6) 239 | public_suffix (4.0.6) 240 | racc (1.7.3) 241 | rb-fsevent (0.11.0) 242 | rb-inotify (0.10.1) 243 | ffi (~> 1.0) 244 | rexml (3.3.6) 245 | strscan 246 | rouge (3.26.0) 247 | ruby-enum (0.9.0) 248 | i18n 249 | ruby2_keywords (0.0.5) 250 | rubyzip (2.3.2) 251 | safe_yaml (1.0.5) 252 | sass (3.7.4) 253 | sass-listen (~> 4.0.0) 254 | sass-listen (4.0.0) 255 | rb-fsevent (~> 0.9, >= 0.9.4) 256 | rb-inotify (~> 0.9, >= 0.9.7) 257 | sawyer (0.8.2) 258 | addressable (>= 2.3.5) 259 | faraday (> 0.8, < 2.0) 260 | simpleidn (0.2.1) 261 | unf (~> 0.1.4) 262 | strscan (3.1.0) 263 | terminal-table (1.8.0) 264 | unicode-display_width (~> 1.1, >= 1.1.1) 265 | thread_safe (0.3.6) 266 | typhoeus (1.4.0) 267 | ethon (>= 0.9.0) 268 | tzinfo (1.2.10) 269 | thread_safe (~> 0.1) 270 | unf (0.1.4) 271 | unf_ext 272 | unf_ext (0.0.8) 273 | unicode-display_width (1.8.0) 274 | zeitwerk (2.6.6) 275 | 276 | PLATFORMS 277 | universal-darwin-20 278 | x86_64-linux 279 | 280 | DEPENDENCIES 281 | github-pages (~> 219) 282 | jekyll-feed (~> 0.12) 283 | jekyll-redirect-from 284 | minima (~> 2.5) 285 | tzinfo (~> 1.2) 286 | tzinfo-data 287 | wdm (~> 0.1.1) 288 | 289 | BUNDLED WITH 290 | 2.2.20 291 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Jekyll! 2 | # 3 | # This config file is meant for settings that affect your whole blog, values 4 | # which you are expected to set up once and rarely edit after that. If you find 5 | # yourself editing this file very often, consider using Jekyll's data files 6 | # feature for the data you need to update frequently. 7 | # 8 | # For technical reasons, this file is *NOT* reloaded automatically when you use 9 | # 'bundle exec jekyll serve'. If you change this file, please restart the server process. 10 | # 11 | # If you need help with YAML syntax, here are some quick references for you: 12 | # https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml 13 | # https://learnxinyminutes.com/docs/yaml/ 14 | # 15 | # Site settings 16 | # These are used to personalize your new site. If you look in the HTML files, 17 | # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. 18 | # You can create any custom variable you would like, and they will be accessible 19 | # in the templates via {{ site.myvariable }}. 20 | 21 | title: mlatu 22 | email: mlatu@brightlysalty.33mail.com 23 | description: The mlatu programming language 24 | domain: "mlatu-lang.github.io" 25 | url: "https://mlatu-lang.github.io" 26 | baseurl: "/mlatu" 27 | github_username: mlatu-lang 28 | 29 | permalink: pretty 30 | 31 | 32 | # Build settings 33 | remote_theme: pmarsceill/just-the-docs 34 | plugins: 35 | - jekyll-feed 36 | - jekyll-redirect-from 37 | 38 | # Exclude from processing. 39 | # The following items will not be processed, by default. 40 | # Any item listed under the `exclude:` key here will be automatically added to 41 | # the internal "default list". 42 | # 43 | # Excluded items can be processed by explicitly listing the directories or 44 | # their entries' file path in the `include:` list. 45 | # 46 | # exclude: 47 | # - .sass-cache/ 48 | # - .jekyll-cache/ 49 | # - gemfiles/ 50 | # - Gemfile 51 | # - Gemfile.lock 52 | # - node_modules/ 53 | # - vendor/bundle/ 54 | # - vendor/cache/ 55 | # - vendor/gems/ 56 | # - vendor/ruby/ 57 | 58 | search_enabled: false 59 | heading_anchors: true 60 | nav_sort: case_sensitive 61 | back_to_top: true 62 | back_to_top_text: "Back to top" 63 | last_edit_timestamp: true 64 | last_edit_time_format: "%b %e %Y at %I:%M %p" 65 | gh_edit_link: true 66 | gh_edit_link_text: "Edit this page on GitHub." 67 | gh_edit_repository: "https://github.com/mlatu-lang/mlatu" 68 | gh_edit_branch: "main" 69 | gh_edit_source: "docs" 70 | gh_edit_view_mode: "tree" 71 | color_scheme: dark 72 | -------------------------------------------------------------------------------- /docs/editor.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Editor 4 | nav_order: 3 5 | --- 6 | 7 | # The Editor and Interface 8 | 9 | Let's look at the structured editor and interface for mlatu and how to use them. 10 | 11 | ## The Editor 12 | 13 | You can start start by running `mlatu edit file.mlb` (`mlb` is the extension for mlatu binary files, and `mlt` for mlatu text files). This will open or create a blank binary mlatu file and start up the editor. 14 | 15 | You will immediately notice that there are two sides, the pattern on the left and the replacement on the right. These are the two parts of your first rule. The left and right arrow keys can be used to navigate between the two sides. 16 | 17 | To add a new word to the pattern, start by pressing the space key. This will replace the filename at the top with a dialog where you can type your word. Type another space to enter the word into the pattern. 18 | 19 | You can thus insert any amount of words in the pattern and replacement sides, and navigate up and down the list by using the - you guessed it - up and down keys. You will notice that you cannot insert a quotation with this method; this is intentional. Instead of writing quotations with parentheses, you can create them with the primitive commands. 20 | 21 | The editor's basic commands include `CTRL-W` to save the file and `ESC` to exit. But there are also six primitive commands based on the six primitives of mlatu. They have the same effects on the pattern or replacement sides as the primitves would if placed above the location of the cursor. 22 | 23 | A new empty rule can be created by `CTRL-Space` and the rules can be navigated with the left and right arrows. `CTRL-R` will remove a rule. 24 | 25 | ## The Interface 26 | 27 | The interface behaves very similarly to the editor, except only the left side is editable. The right side is reserved for the output of rewriting the input according to the loaded rulis. 28 | 29 | The left side can be manipulated with the primitives just like in the editor, and additional binary or text files can be loaded on the command line. 30 | 31 | *This is the end of the tutorial for now and it should be enough to get started with mlatu. Feel free to ask any questions or get more help on the Discord server, which is linked on the readme.* 32 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlatu-lang/mlatu/5a8d78d7affa92cc069ed54ffefa652e5fcbd708/docs/favicon.ico -------------------------------------------------------------------------------- /docs/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Introduction 4 | nav_order: 1 5 | permalink: / 6 | redirect_from: 7 | - /introduction 8 | --- 9 | 10 | # Introduction to mlatu 11 | 12 | mlatu is a very simple programming language, at its core. All you need to understand are terms and rules. 13 | 14 | A mlatu term is either a word or a quotation. A word is a bit of text without whitespace that acts as an identifier. A quotation is a series of terms wrapped in parentheses, and opaquely groups the terms together. 15 | 16 | A mlatu rule is a left and right side, each of which contain a series of terms. Including a rule tells the mlatu implementation to replace all instances of the left side (the pattern) with the right side (the replacement). mlatu rules can be used as functions, predicates, or macros. 17 | 18 | This conceptually unified system is computationally universal, which offers similar advantages to those of Lisp - homoiconic code-data manipulation in a simple syntax - with the addition of concatenativity: any series of terms can be concatenated with any other series of terms to have an output equivalent to the concatenation of their inputs. 19 | 20 | [Onward, to the six primitives of mlatu!](primitives.md) 21 | 22 | (Since this tutorial is unfinished, you can find a briefer one [here](tutorial.md)] -------------------------------------------------------------------------------- /docs/primitives.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Primitives 4 | nav_order: 2 5 | --- 6 | 7 | # The Primitives 8 | 9 | There are six primitives of mlatu which have various effects on the terms below them. 10 | 11 | 12 | ### Removing a term 13 | 14 | `r` or `remove` removes the term below it: 15 | 16 | ``` 17 | -------| Input |--------------| Output |------- 18 | | x | | 19 | | remove | | 20 | | y | x | 21 | | z | z | 22 | ``` 23 | 24 | ### Duplicating a term 25 | 26 | `d` or `duplicate` duplicates the term below it: 27 | 28 | ``` 29 | -------| Input |--------------| Output |------- 30 | | x | x | 31 | | dup | y | 32 | | y | y | 33 | | z | z | 34 | ``` 35 | 36 | ### Swapping terms 37 | 38 | `s` or `swap` swaps the two terms below it: 39 | 40 | ``` 41 | -------| Input |--------------| Output |------- 42 | | x | | 43 | | swap | x | 44 | | y | z | 45 | | z | y | 46 | ``` 47 | 48 | ### Quoting a term 49 | 50 | `q` or `quote` quotes the term below it: 51 | 52 | ``` 53 | -------| Input |--------------| Output |------- 54 | | x | | 55 | | quote | x | 56 | | y | ( y ) | 57 | | z | z | 58 | ``` 59 | 60 | ### Unquoting a quotation 61 | 62 | `u` or `unquote` unquotes the quotation below it: 63 | 64 | ``` 65 | -------| Input |--------------| Output |------- 66 | | x | | 67 | | unquote | x | 68 | | ( y ) | y | 69 | | z | z | 70 | ``` 71 | 72 | ### Concatenating quotations 73 | 74 | `c` or `concat` concaatenates the two quotations below it: 75 | 76 | ``` 77 | -------| Input |--------------| Output |------- 78 | | x | | 79 | | concat | | 80 | | ( y ) | x | 81 | | ( z ) | ( y z ) | 82 | ``` 83 | 84 | [Onward, to the mlatu structured editor!](editor.md) 85 | 86 | -------------------------------------------------------------------------------- /docs/tutorial.md: -------------------------------------------------------------------------------- 1 | # an informal tutorial for mlatu 2 | 3 | mlatu is a language that rewrites some terms to other terms 4 | these terms are either whitespace-delimited atoms (unless they're one of the 6 primitives, in which case they're always parsed as one character), or quotations (`quotation ::= '(' term* ')'`) 5 | a quotation is a collection of AST nodes (the same as quotations in joy, and a list of symbols in lisp terms) 6 | 7 | there are 6 primitive rewriting functions: `<>,~+-,` which all act on quotes (see [here](https://hackmd.io/pgI_ipgfSMWc-MQvSgmC2g#The-Church-Rosser-theorem) (note: is this even necessary when mlatu, by design, already doesn't follow the Church-Rosser theorem? I don't know)) 8 | the symbol `|->` means "rewrites to" and was completely made up by ISO 9 | if we have quotes `x` and `y`, 10 | ``` 11 | x+ |-> x x 12 | x- |-> 13 | x> |-> (x) 14 | x y~ |-> y x 15 | ``` 16 | if you are even slightly competent at math, you will notice that that is only 4 out of the 6 functions. here are the remaining two: 17 | `<` unwraps a quote. that means that `(foo bar baz)<` will rewrite to `foo bar baz` 18 | `,` catenates two quotes. `(foo bar) (baz) ,` will rewrite to `(foo bar baz)` 19 | 20 | you probably have realized that mlatu is RPN. here is the full evaluation order: 21 | ``` 22 | 1. find the longest rewrite rule starting at the first term 23 | a. if it was found, rewrite it, and goto 1. 24 | b. if it was not found, look for the longest rewrite rule starting at the second term 25 | i. if it was found, rewrite it, and goto 1. 26 | ii. if it was not found, continue looking with the third term etc. 27 | iii. if no rule is found that matches anywhere, the rewriting is finished 28 | ``` 29 | 30 | you can also define rules yourself: 31 | ``` 32 | a b = c. 33 | ``` 34 | is a rule that matches on `a b`, and will rewrite it to `c`. 35 | user-defined rules may not match on quotes. 36 | 37 | for more examples of defined rules, see [better-nat.mlt](https://github.com/mlatu-lang/rebel-libraries/blob/main/better-nat.mlt), an implementation of peano numerals 38 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2021-10-23" 3 | components = ["rustfmt", "rust-std", "rustc", "cargo", "rust-docs", "clippy"] -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | 3 | binop_separator = "Front" 4 | blank_lines_lower_bound = 0 5 | blank_lines_upper_bound = 1 6 | brace_style = "SameLineWhere" 7 | color = "Auto" 8 | combine_control_expr = true 9 | comment_width = 80 10 | condense_wildcard_suffixes = true 11 | control_brace_style = "AlwaysSameLine" 12 | disable_all_formatting = false 13 | edition = "2018" 14 | empty_item_single_line = true 15 | enum_discrim_align_threshold = 0 16 | error_on_line_overflow = false 17 | error_on_unformatted = false 18 | fn_args_layout = "Compressed" 19 | fn_single_line = true 20 | force_explicit_abi = true 21 | force_multiline_blocks = false 22 | format_code_in_doc_comments = true 23 | format_macro_matchers = true 24 | format_macro_bodies = true 25 | format_strings = true 26 | hard_tabs = false 27 | hide_parse_errors = false 28 | ignore = [] 29 | imports_indent = "Visual" 30 | imports_layout = "Horizontal" 31 | indent_style = "Visual" 32 | inline_attribute_width = 0 33 | license_template_path = "" 34 | match_arm_blocks = false 35 | match_arm_leading_pipes = "Always" 36 | match_block_trailing_comma = true 37 | max_width = 100 38 | merge_derives = true 39 | imports_granularity = "Module" 40 | newline_style = "Unix" 41 | normalize_comments = true 42 | normalize_doc_attributes = true 43 | overflow_delimited_expr = true 44 | remove_nested_parens = true 45 | reorder_impl_items = true 46 | reorder_imports = true 47 | group_imports = "StdExternalCrate" 48 | reorder_modules = true 49 | report_fixme = "Never" 50 | report_todo = "Never" 51 | skip_children = false 52 | space_after_colon = false 53 | space_before_colon = false 54 | spaces_around_ranges = false 55 | struct_field_align_threshold = 0 56 | struct_lit_single_line = true 57 | tab_spaces = 2 58 | trailing_comma = "Always" 59 | trailing_semicolon = false 60 | type_punctuation_density = "Compressed" 61 | use_field_init_shorthand = true 62 | use_small_heuristics = "Max" 63 | use_try_shorthand = true 64 | version = "Two" 65 | where_single_line = true 66 | wrap_comments = true -------------------------------------------------------------------------------- /src/editor.rs: -------------------------------------------------------------------------------- 1 | use std::io::{stdout, Write}; 2 | use std::path::PathBuf; 3 | use std::sync::Arc; 4 | 5 | use crossterm::event::KeyCode::{Backspace, Char, Delete, Down, Esc, Left, Right, Up}; 6 | use crossterm::event::KeyModifiers; 7 | use crossterm::queue; 8 | use im::{vector, Vector}; 9 | use mlatu_lib::{parse, pretty, Engine, Rule, Term}; 10 | use tokio::sync::RwLock; 11 | 12 | use crate::view::{State, View}; 13 | 14 | pub struct Editor { 15 | engine:Engine, 16 | path:PathBuf, 17 | view:View, 18 | rules:Vec>>, 19 | rule_idx:usize, 20 | should_quit:bool, 21 | state:State, 22 | } 23 | 24 | fn die(e:&str) { 25 | std::mem::drop(queue!(stdout(), crossterm::terminal::Clear(crossterm::terminal::ClearType::All))); 26 | std::mem::drop(crossterm::terminal::disable_raw_mode()); 27 | std::mem::drop(stdout().flush()); 28 | panic!("{}", e) 29 | } 30 | 31 | impl Editor { 32 | /// # Errors 33 | /// 34 | /// Will return `Err` if there was an error constructing a terminal 35 | pub fn new(engine:Engine, path:PathBuf, original_rules:Vector) -> Result { 36 | crossterm::terminal::enable_raw_mode().map_err(|e| e.to_string())?; 37 | let should_quit = false; 38 | let rule_idx = 0; 39 | let (rules, state) = if original_rules.is_empty() { 40 | (vec![Arc::new(RwLock::new(Rule { redex:Vector::new(), reduction:Vector::new() }))], 41 | State::AtLeft) 42 | } else { 43 | let mut rules = Vec::new(); 44 | let state = if original_rules[0].redex.is_empty() { State::AtLeft } else { State::InLeft(0) }; 45 | for rule in original_rules { 46 | rules.push(Arc::new(RwLock::new(rule.clone()))); 47 | } 48 | (rules, state) 49 | }; 50 | let default_status = 51 | format!("{} (rule {}/{})", path.to_string_lossy(), rule_idx + 1, rules.len()); 52 | let view = View::new(Arc::clone(&rules[rule_idx]), 53 | ("| Pattern |".to_string(), "| Replacement |".to_string()), 54 | default_status).map_err(|e| e.to_string())?; 55 | Ok(Self { engine, path, view, rules, rule_idx, should_quit, state }) 56 | } 57 | 58 | pub async fn run(&mut self) { 59 | loop { 60 | if let Err(error) = self.view 61 | .refresh_screen(|term| pretty::term(&self.engine, term.clone()), 62 | &self.state, 63 | self.should_quit) 64 | .await 65 | { 66 | die(&error.to_string()); 67 | } 68 | if self.should_quit { 69 | let _result = crossterm::terminal::disable_raw_mode(); 70 | break 71 | } 72 | if let Err(error) = self.process_keypress().await { 73 | die(&error); 74 | } 75 | } 76 | } 77 | 78 | async fn save(&mut self) -> Result<(), String> { 79 | let mut rs = Vector::new(); 80 | for rule in &self.rules { 81 | let guard = rule.read().await; 82 | rs.push_back(guard.clone()); 83 | } 84 | std::fs::write(self.path.clone(), &pretty::rules(&self.engine, rs)).map_err(|e| e.to_string()) 85 | } 86 | 87 | async fn set_left_view(&mut self, index:usize) -> Result<(), String> { 88 | let rule = &self.rules[self.rule_idx]; 89 | let default_status = 90 | format!("{} (rule {}/{})", self.path.to_string_lossy(), self.rule_idx + 1, self.rules.len()); 91 | let guard = rule.read().await; 92 | self.state = if guard.redex.is_empty() { 93 | State::AtLeft 94 | } else { 95 | State::InLeft(index.min(guard.redex.len() - 1)) 96 | }; 97 | self.view = View::new(Arc::clone(rule), 98 | ("| Pattern |".to_string(), "| Replacement |".to_string()), 99 | default_status).map_err(|e| e.to_string())?; 100 | Ok(()) 101 | } 102 | 103 | async fn set_right_view(&mut self, index:usize) -> Result<(), String> { 104 | let rule = &self.rules[self.rule_idx]; 105 | let default_status = 106 | format!("{} (rule {}/{})", self.path.to_string_lossy(), self.rule_idx + 1, self.rules.len()); 107 | let guard = rule.read().await; 108 | self.state = if guard.reduction.is_empty() { 109 | State::AtRight 110 | } else { 111 | State::InRight(index.min(guard.reduction.len() - 1)) 112 | }; 113 | self.view = View::new(Arc::clone(rule), 114 | ("| Pattern |".to_string(), "| Replacement |".to_string()), 115 | default_status).map_err(|e| e.to_string())?; 116 | Ok(()) 117 | } 118 | 119 | async fn process_left(&mut self) -> Result<(), String> { 120 | match self.state { 121 | | State::AtRight => { 122 | let guard = self.view.read().await; 123 | self.state = if guard.redex.is_empty() { State::AtLeft } else { State::InLeft(0) }; 124 | }, 125 | | State::InRight(index) => { 126 | let guard = self.view.read().await; 127 | self.state = if guard.redex.is_empty() { 128 | State::AtLeft 129 | } else { 130 | State::InLeft(index.min(guard.redex.len() - 1)) 131 | } 132 | }, 133 | | State::AtLeft if self.rule_idx > 0 => { 134 | self.rule_idx -= 1; 135 | self.set_right_view(0).await?; 136 | }, 137 | | State::InLeft(index) if self.rule_idx > 0 => { 138 | self.rule_idx -= 1; 139 | self.set_right_view(index).await?; 140 | }, 141 | | _ => {}, 142 | }; 143 | Ok(()) 144 | } 145 | 146 | async fn process_right(&mut self) -> Result<(), String> { 147 | match self.state { 148 | | State::AtLeft => { 149 | let guard = self.view.read().await; 150 | self.state = if guard.reduction.is_empty() { State::AtRight } else { State::InRight(0) }; 151 | }, 152 | | State::InLeft(index) => { 153 | let guard = self.view.read().await; 154 | self.state = if guard.reduction.is_empty() { 155 | State::AtRight 156 | } else { 157 | State::InRight(index.min(guard.reduction.len() - 1)) 158 | } 159 | }, 160 | | State::AtRight if self.rule_idx < (self.rules.len() - 1) => { 161 | self.rule_idx += 1; 162 | self.set_left_view(0).await?; 163 | }, 164 | | State::InRight(index) if self.rule_idx < (self.rules.len() - 1) => { 165 | self.rule_idx += 1; 166 | self.set_left_view(index).await?; 167 | }, 168 | | _ => {}, 169 | }; 170 | Ok(()) 171 | } 172 | 173 | async fn input_word(&mut self, s:String, state:Box) { 174 | if let Ok(term) = parse::term(&self.engine, &s) { 175 | let mut guard = self.view.write().await; 176 | 177 | match *state { 178 | | State::AtLeft => { 179 | guard.redex = vector![term.clone()]; 180 | self.state = State::InLeft(0); 181 | }, 182 | | State::AtRight => { 183 | guard.reduction = vector![term.clone()]; 184 | self.state = State::InRight(0); 185 | }, 186 | | State::InLeft(index) => { 187 | guard.redex.insert(index, term.clone()); 188 | self.state = State::InLeft(index); 189 | }, 190 | | State::InRight(index) => { 191 | guard.reduction.insert(index, term.clone()); 192 | self.state = State::InRight(index); 193 | }, 194 | | _ => {}, 195 | } 196 | } 197 | } 198 | 199 | async fn concat_left(&mut self, index:usize) { 200 | let mut guard = self.view.write().await; 201 | if let Term::Quote(mut terms) = guard.redex[index].clone() { 202 | if let Some(Term::Quote(other_terms)) = guard.redex.get(index + 1).cloned() { 203 | terms.extend(other_terms); 204 | guard.redex.remove(index); 205 | guard.redex[index] = Term::make_quote(&self.engine, terms).clone(); 206 | } 207 | } 208 | } 209 | 210 | async fn concat_right(&mut self, index:usize) { 211 | let mut guard = self.view.write().await; 212 | if let Term::Quote(mut terms) = guard.reduction[index].clone() { 213 | if let Some(Term::Quote(other_terms)) = guard.reduction.get(index + 1).cloned() { 214 | terms.extend(other_terms); 215 | guard.reduction.remove(index); 216 | guard.reduction[index] = Term::make_quote(&self.engine, terms).clone(); 217 | } 218 | } 219 | } 220 | 221 | async fn unquote_left(&mut self, index:usize) { 222 | let mut guard = self.view.write().await; 223 | if let Term::Quote(terms) = guard.redex[index].clone() { 224 | guard.redex.remove(index); 225 | let mut i = index; 226 | for term in terms { 227 | guard.redex.insert(i, term); 228 | i += 1; 229 | } 230 | self.state = if guard.redex.is_empty() { 231 | State::AtLeft 232 | } else { 233 | State::InLeft(index.min(guard.redex.len() - 1)) 234 | }; 235 | } 236 | } 237 | 238 | async fn unquote_right(&mut self, index:usize) { 239 | let mut guard = self.view.write().await; 240 | if let Term::Quote(terms) = guard.reduction[index].clone() { 241 | guard.reduction.remove(index); 242 | let mut i = index; 243 | for term in terms { 244 | guard.reduction.insert(i, term); 245 | i += 1; 246 | } 247 | self.state = if guard.reduction.is_empty() { 248 | State::AtRight 249 | } else { 250 | State::InRight(index.min(guard.reduction.len() - 1)) 251 | }; 252 | } 253 | } 254 | 255 | async fn remove_left(&mut self, index:usize) { 256 | let mut guard = self.view.write().await; 257 | guard.redex.remove(index); 258 | self.state = if guard.redex.is_empty() { 259 | State::AtLeft 260 | } else { 261 | State::InLeft(index.min(guard.redex.len() - 1)) 262 | }; 263 | } 264 | 265 | async fn remove_right(&mut self, index:usize) { 266 | let mut guard = self.view.write().await; 267 | guard.reduction.remove(index); 268 | self.state = if guard.reduction.is_empty() { 269 | State::AtRight 270 | } else { 271 | State::InRight(index.min(guard.reduction.len() - 1)) 272 | }; 273 | } 274 | 275 | async fn process_keypress(&mut self) -> Result<(), String> { 276 | let event = self.view.read_key().await.map_err(|e| e.to_string())?; 277 | match (event.code, event.modifiers) { 278 | | (Esc, _) => self.should_quit = true, 279 | | (Char('w'), KeyModifiers::CONTROL) => self.save().await?, 280 | | (Char('r'), KeyModifiers::CONTROL) => { 281 | if self.rules.len() == 1 { 282 | self.rules[0] = 283 | Arc::new(RwLock::new(Rule { redex:Vector::new(), reduction:Vector::new() })); 284 | } else { 285 | self.rules.remove(self.rule_idx); 286 | self.rule_idx = self.rule_idx.min(self.rules.len() - 1); 287 | } 288 | self.set_left_view(0).await?; 289 | }, 290 | | (Char(' '), KeyModifiers::CONTROL) => { 291 | self.rules 292 | .insert(self.rule_idx, 293 | Arc::new(RwLock::new(Rule { redex:Vector::new(), reduction:Vector::new() }))); 294 | self.set_left_view(0).await?; 295 | }, 296 | | (Char(c), _) => match (c, self.state.clone()) { 297 | | (' ', State::Editing(s, state)) => self.input_word(s, state).await, 298 | | (c, State::Editing(s, state)) => { 299 | let mut s = s; 300 | s.push(c); 301 | self.state = State::Editing(s, state); 302 | }, 303 | | ('>', State::InLeft(index)) => { 304 | let mut guard = self.view.write().await; 305 | guard.redex[index] = 306 | Term::make_quote(&self.engine, vector![guard.redex[index].clone()]).clone(); 307 | }, 308 | | ('>', State::InRight(index)) => { 309 | let mut guard = self.view.write().await; 310 | guard.reduction[index] = 311 | Term::make_quote(&self.engine, vector![guard.reduction[index].clone()]).clone(); 312 | }, 313 | | ('-', State::InLeft(index)) => self.remove_left(index).await, 314 | | ('-', State::InRight(index)) => self.remove_right(index).await, 315 | | ('+', State::InLeft(index)) => { 316 | let mut guard = self.view.write().await; 317 | let term = guard.redex[index].clone(); 318 | guard.redex.insert(index, term); 319 | self.state = State::InLeft(index.min(guard.redex.len() - 1)); 320 | }, 321 | | ('+', State::InRight(index)) => { 322 | let mut guard = self.view.write().await; 323 | let term = guard.reduction[index].clone(); 324 | guard.reduction.insert(index, term); 325 | self.state = State::InRight(index.min(guard.reduction.len() - 1)); 326 | }, 327 | | ('~', State::InLeft(index)) => 328 | if index > 0 { 329 | let mut guard = self.view.write().await; 330 | guard.redex.swap(index, index - 1); 331 | }, 332 | | ('~', State::InRight(index)) => 333 | if index > 0 { 334 | let mut guard = self.view.write().await; 335 | guard.reduction.swap(index, index - 1); 336 | }, 337 | | (',', State::InLeft(index)) => self.concat_left(index).await, 338 | | (',', State::InRight(index)) => self.concat_right(index).await, 339 | | ('<', State::InLeft(index)) => self.unquote_left(index).await, 340 | | ('<', State::InRight(index)) => self.unquote_right(index).await, 341 | | (' ', _) => self.state = State::Editing(String::new(), Box::new(self.state.clone())), 342 | | _ => {}, 343 | }, 344 | | (Backspace | Delete, _) => match self.state.clone() { 345 | | State::Editing(s, state) => { 346 | let mut s = s; 347 | s.pop(); 348 | self.state = State::Editing(s, state); 349 | }, 350 | | _ => {}, 351 | }, 352 | | (Left, _) => self.process_left().await?, 353 | | (Right, _) => self.process_right().await?, 354 | | (Down, _) => match self.state { 355 | | State::InLeft(index) => self.state = State::InLeft(index.saturating_sub(1)), 356 | | State::InRight(index) => self.state = State::InRight(index.saturating_sub(1)), 357 | | _ => {}, 358 | }, 359 | | (Up, _) => match self.state { 360 | | State::InLeft(index) => { 361 | let guard = self.view.read().await; 362 | self.state = State::InLeft((index + 1).min(guard.redex.len() - 1)); 363 | }, 364 | | State::InRight(index) => { 365 | let guard = self.view.read().await; 366 | self.state = State::InRight((index + 1).min(guard.reduction.len() - 1)); 367 | }, 368 | | _ => {}, 369 | }, 370 | | _ => {}, 371 | } 372 | Ok(()) 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /src/interactive.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::io::stdout; 3 | use std::sync::Arc; 4 | 5 | use crossterm::execute; 6 | use im::{vector, Vector}; 7 | use mlatu_lib::{parse, pretty, rewrite, Engine, Rule, Term}; 8 | use tokio::sync::RwLock; 9 | 10 | use crate::view::{State, View}; 11 | 12 | pub struct Interactive { 13 | rules:Vector, 14 | engine:Engine, 15 | view:View, 16 | should_quit:bool, 17 | state:State, 18 | } 19 | 20 | fn die(e:&str) -> ! { 21 | std::mem::drop(execute!(stdout(), 22 | crossterm::terminal::Clear(crossterm::terminal::ClearType::All))); 23 | std::mem::drop(crossterm::terminal::disable_raw_mode()); 24 | panic!("{}", e) 25 | } 26 | 27 | impl Interactive { 28 | /// # Errors 29 | /// 30 | /// Will return `Err` if there was an error constructing a terminal 31 | pub fn new(engine:Engine, rules:Vector) -> io::Result { 32 | crossterm::terminal::enable_raw_mode()?; 33 | let should_quit = false; 34 | let rule = Arc::new(RwLock::new(Rule { redex:Vector::new(), reduction:Vector::new() })); 35 | let state = State::AtLeft; 36 | let default_status = "mlatu interface".to_string(); 37 | let view = View::new(Arc::clone(&rule), 38 | ("| Input |".to_string(), "| Output |".to_string()), 39 | default_status)?; 40 | Ok(Self { rules, engine, view, should_quit, state }) 41 | } 42 | 43 | /// # Panics 44 | /// 45 | /// Panics if there was an error displaying to the screen, an error processing 46 | /// keypresses, or an error handling the Prolog interface. 47 | /// 48 | /// # Errors 49 | /// 50 | /// Returns `Err` if there was an IO error reading keypresses 51 | pub async fn run(&mut self) -> io::Result<()> { 52 | loop { 53 | if let Err(error) = self.view 54 | .refresh_screen(|term| pretty::term(&self.engine, term.clone()), 55 | &self.state, 56 | self.should_quit) 57 | .await 58 | { 59 | die(&error.to_string()); 60 | } 61 | if self.should_quit { 62 | let _result = crossterm::terminal::disable_raw_mode(); 63 | break Ok(()) 64 | } 65 | self.process_keypress().await?; 66 | } 67 | } 68 | 69 | async fn remove(&mut self, index:usize) { 70 | let mut guard = self.view.write().await; 71 | guard.redex.remove(index); 72 | self.state = if guard.redex.is_empty() { 73 | State::AtLeft 74 | } else { 75 | State::InLeft(index.min(guard.redex.len() - 1)) 76 | }; 77 | guard.reduction = rewrite(&self.engine, &self.rules, guard.redex.clone()); 78 | } 79 | 80 | async fn quote(&mut self, index:usize) { 81 | let mut guard = self.view.write().await; 82 | guard.redex[index] = 83 | Term::make_quote(&self.engine, vector![guard.redex[index].clone()]).clone(); 84 | guard.reduction = rewrite(&self.engine, &self.rules, guard.redex.clone()); 85 | } 86 | 87 | async fn swap(&mut self, index:usize) { 88 | let mut guard = self.view.write().await; 89 | if index > 0 { 90 | guard.redex.swap(index, index - 1); 91 | guard.reduction = rewrite(&self.engine, &self.rules, guard.redex.clone()); 92 | } 93 | } 94 | 95 | async fn dup(&mut self, index:usize) { 96 | let mut guard = self.view.write().await; 97 | let term = guard.redex[index].clone(); 98 | guard.redex.insert(index, term); 99 | guard.reduction = rewrite(&self.engine, &self.rules, guard.redex.clone()); 100 | } 101 | 102 | async fn concat(&mut self, index:usize) { 103 | let mut guard = self.view.write().await; 104 | if let Term::Quote(mut terms) = guard.redex[index].clone() { 105 | if let Some(Term::Quote(other_terms)) = guard.redex.get(index + 1) { 106 | terms.extend(other_terms.clone()); 107 | guard.redex.remove(index); 108 | guard.redex[index] = Term::make_quote(&self.engine, terms).clone(); 109 | guard.reduction = rewrite(&self.engine, &self.rules, guard.redex.clone()); 110 | } 111 | } 112 | } 113 | 114 | async fn unquote(&mut self, index:usize) { 115 | let mut guard = self.view.write().await; 116 | if let Term::Quote(terms) = guard.redex[index].clone() { 117 | guard.redex.remove(index); 118 | let mut i = index; 119 | for term in terms { 120 | guard.redex.insert(i, term); 121 | i += 1; 122 | } 123 | self.state = if guard.redex.is_empty() { 124 | State::AtLeft 125 | } else { 126 | State::InLeft(index.min(guard.redex.len() - 1)) 127 | }; 128 | guard.reduction = rewrite(&self.engine, &self.rules, guard.redex.clone()); 129 | } 130 | } 131 | 132 | async fn process_keypress(&mut self) -> io::Result<()> { 133 | use crossterm::event::KeyCode::{Backspace, Char, Delete, Down, Esc, Up}; 134 | 135 | let event = self.view.read_key().await?; 136 | match (event.code, event.modifiers) { 137 | | (Esc, _) => self.should_quit = true, 138 | | (Char(c), _) => match (c, self.state.clone()) { 139 | | (' ', State::Editing(s, state)) => 140 | if let Ok(term) = parse::term(&self.engine, &s) { 141 | let mut guard = self.view.write().await; 142 | match *state { 143 | | State::AtLeft => { 144 | guard.redex = vector![term.clone()]; 145 | self.state = State::InLeft(0); 146 | guard.reduction = rewrite(&self.engine, &self.rules, guard.redex.clone()); 147 | }, 148 | | State::InLeft(index) => { 149 | guard.redex.insert(index, term.clone()); 150 | self.state = State::InLeft(index); 151 | guard.reduction = rewrite(&self.engine, &self.rules, guard.redex.clone()); 152 | }, 153 | | _ => {}, 154 | } 155 | }, 156 | | (c, State::Editing(s, state)) => { 157 | let mut s = s; 158 | s.push(c); 159 | self.state = State::Editing(s, state); 160 | }, 161 | | ('-', State::InLeft(index)) => self.remove(index).await, 162 | | ('>', State::InLeft(index)) => self.quote(index).await, 163 | | ('~', State::InLeft(index)) => self.swap(index).await, 164 | | ('+', State::InLeft(index)) => self.dup(index).await, 165 | | (',', State::InLeft(index)) => self.concat(index).await, 166 | | ('<', State::InLeft(index)) => self.unquote(index).await, 167 | | (' ', State::InLeft(index)) => 168 | self.state = State::Editing(String::new(), Box::new(State::InLeft(index))), 169 | | (' ', State::AtLeft) => 170 | self.state = State::Editing(String::new(), Box::new(State::AtLeft)), 171 | | _ => {}, 172 | }, 173 | | (Backspace | Delete, _) => match self.state.clone() { 174 | | State::Editing(s, state) => { 175 | let mut s = s; 176 | s.pop(); 177 | self.state = State::Editing(s, state); 178 | }, 179 | | _ => {}, 180 | }, 181 | | (Up, _) => match self.state { 182 | | State::InLeft(index) => { 183 | let guard = self.view.read().await; 184 | self.state = if guard.redex.is_empty() { 185 | State::AtLeft 186 | } else { 187 | State::InLeft((index + 1).min(guard.redex.len() - 1)) 188 | }; 189 | }, 190 | | _ => {}, 191 | }, 192 | | (Down, _) => match self.state { 193 | | State::InLeft(index) => { 194 | let guard = self.view.read().await; 195 | self.state = if guard.redex.is_empty() { 196 | State::AtLeft 197 | } else { 198 | State::InLeft((index.saturating_sub(1)).min(guard.redex.len() - 1)) 199 | }; 200 | }, 201 | | _ => {}, 202 | }, 203 | | _ => {}, 204 | } 205 | Ok(()) 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(stdio_locked)] 2 | #![deny(clippy::correctness)] 3 | #![warn(clippy::suspicious, 4 | clippy::style, 5 | clippy::complexity, 6 | clippy::perf, 7 | clippy::pedantic, 8 | clippy::nursery, 9 | clippy::as_conversions, 10 | clippy::clone_on_ref_ptr, 11 | clippy::create_dir, 12 | clippy::decimal_literal_representation, 13 | clippy::exit, 14 | clippy::filetype_is_file, 15 | clippy::float_cmp_const, 16 | clippy::if_then_some_else_none, 17 | clippy::lossy_float_literal, 18 | clippy::mem_forget, 19 | clippy::mod_module_files, 20 | clippy::rc_buffer, 21 | clippy::rc_mutex, 22 | clippy::unwrap_used, 23 | clippy::verbose_file_reads)] 24 | #![allow(clippy::future_not_send)] 25 | 26 | mod editor; 27 | mod interactive; 28 | mod view; 29 | 30 | pub use editor::Editor; 31 | pub use interactive::Interactive; 32 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(with_options)] 2 | 3 | use std::path::{Path, PathBuf}; 4 | 5 | use clap::{arg, command, Command}; 6 | use im::Vector; 7 | use mlatu::{Editor, Interactive}; 8 | use mlatu_lib::{parse, Engine, Rule}; 9 | 10 | fn load_file(engine:&Engine, filename:&str) -> Result, String> { 11 | let path = Path::new(&filename); 12 | match std::fs::read(path) { 13 | | Ok(bytes) => match String::from_utf8(bytes) { 14 | | Ok(string) => match parse::rules(engine, &string) { 15 | | Ok(rules) => Ok(rules), 16 | | Err(e) => Err(format!("Error while parsing '{}': {}", filename, e)), 17 | }, 18 | | Err(e) => Err(format!("Error in decoding '{}': {}", filename, e)), 19 | }, 20 | | Err(e) => Err(format!("Error while reading '{}': {}", filename, e)), 21 | } 22 | } 23 | 24 | fn load_files(engine:&Engine, files:Vec) -> Result, String> { 25 | let mut rules = Vector::new(); 26 | for file in files { 27 | rules.extend(load_file(engine, &file)?); 28 | } 29 | Ok(rules) 30 | } 31 | 32 | #[tokio::main] 33 | async fn main() -> Result<(), String> { 34 | let matches = command!().propagate_version(true) 35 | .arg(arg!([FILES]).multiple_values(true).help("Rule files to use")) 36 | .subcommand(Command::new("edit").about("the structured editor") 37 | .arg(arg!([FILE]).help("Rule file to \ 38 | edit"))) 39 | .get_matches(); 40 | 41 | let engine = Engine::new(); 42 | 43 | match matches.subcommand() { 44 | | Some(("edit", sub_matches)) => { 45 | let filename = sub_matches.value_of("FILE").unwrap(); 46 | let rules = load_file(&engine, filename)?; 47 | let mut path = PathBuf::from(filename); 48 | let _ = path.set_extension("mlb"); 49 | match path.canonicalize() { 50 | | Ok(path) => Editor::new(engine, path, rules)?.run().await, 51 | | Err(_) => eprintln!("Path could not be canonicalized"), 52 | } 53 | }, 54 | | _ => { 55 | let mut files = Vec::new(); 56 | if let Some(args) = matches.values_of("FILES") { 57 | files.extend(args.map(ToOwned::to_owned)); 58 | } 59 | match load_files(&engine, files) { 60 | | Ok(rules) => match Interactive::new(engine, rules) { 61 | | Ok(mut interactive) => match interactive.run().await { 62 | | Ok(()) => {}, 63 | | Err(error) => eprintln!("{}", error), 64 | }, 65 | | Err(error) => eprintln!("{}", error), 66 | }, 67 | | Err(error) => eprintln!("{}", error), 68 | } 69 | }, 70 | } 71 | 72 | Ok(()) 73 | } 74 | -------------------------------------------------------------------------------- /src/view.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::io::{stdout, Write}; 3 | use std::sync::Arc; 4 | 5 | use crossterm::event::{Event, EventStream, KeyEvent}; 6 | use crossterm::terminal::ClearType; 7 | use crossterm::{cursor, queue, terminal, Command}; 8 | use mlatu_lib::{Rule, Term}; 9 | use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; 10 | use tokio_stream::StreamExt; 11 | 12 | #[derive(Clone)] 13 | pub enum State { 14 | InLeft(usize), 15 | InRight(usize), 16 | AtLeft, 17 | AtRight, 18 | Editing(String, Box), 19 | } 20 | 21 | pub struct View { 22 | width:u16, 23 | height:u16, 24 | sides:Arc>, 25 | labels:(String, String), 26 | default_status:String, 27 | } 28 | 29 | impl View { 30 | pub fn new(sides:Arc>, labels:(String, String), default_status:String) 31 | -> io::Result { 32 | let (width, height) = terminal::size()?; 33 | Ok(Self { width, height, sides, labels, default_status }) 34 | } 35 | 36 | fn queue(command:impl Command) -> io::Result<()> { queue!(stdout(), command) } 37 | 38 | pub fn clear_screen() -> io::Result<()> { Self::queue(terminal::Clear(ClearType::All)) } 39 | 40 | pub fn flush() -> io::Result<()> { stdout().flush() } 41 | 42 | pub async fn read_key(&mut self) -> io::Result { 43 | let mut stream = EventStream::new(); 44 | loop { 45 | match stream.next().await { 46 | | Some(Ok(Event::Key(key))) => return Ok(key), 47 | | Some(Ok(Event::Resize(columns, rows))) => { 48 | self.width = columns; 49 | self.height = rows; 50 | }, 51 | | Some(Ok(Event::Mouse(_))) => {}, 52 | | Some(Err(e)) => return Err(e), 53 | | None => 54 | return Err(io::Error::new(io::ErrorKind::Other, "unexpected end of terminal input")), 55 | } 56 | } 57 | } 58 | 59 | const fn left_half_width(&self, sep_len:u16) -> u16 { 60 | // Width of left box 61 | // half of the width, minus first separator 62 | // = l/2 - s 63 | // where l is the width and s is the separator length 64 | self.width / 2 - sep_len 65 | } 66 | 67 | const fn right_half_width(&self, sep_len:u16) -> u16 { 68 | // Width of right box 69 | // entire width, minus first box, minux all 3 separators 70 | // = l - (l/2 - s) - 3s 71 | // = l - l/s + s - 3s 72 | // = l - l/2 - 2s 73 | // where l is the width and s is the separator length 74 | self.width - self.width / 2 - 2 * sep_len 75 | } 76 | 77 | async fn make_left_half String+Copy>(&self, f:F, row:u16, s:&mut String) { 78 | let guard = self.sides.read().await; 79 | let term = 80 | guard.redex.get(usize::from(self.height - row).saturating_sub(1)).map_or_else(String::new, f); 81 | let width = self.left_half_width(1); 82 | s.push_str(&format!("{0: ^1$}", term, width.into())); 83 | } 84 | 85 | async fn make_right_half String+Copy>(&self, f:F, row:u16, s:&mut String) { 86 | let width = self.right_half_width(1); 87 | let guard = self.sides.read().await; 88 | let term = guard.reduction 89 | .get(usize::from(self.height - row).saturating_sub(1)) 90 | .map_or_else(String::new, f); 91 | s.push_str(&format!("{0: ^1$}", term, width.into())); 92 | } 93 | 94 | async fn get_target String+Copy>(&self, f:F, state:&State) -> (u16, u16) { 95 | let guard = self.sides.read().await; 96 | match state { 97 | | State::InLeft(index) => { 98 | let term = guard.redex.get(*index).expect("bounds check failed"); 99 | let p_t = f(term); 100 | (self.width / 4 101 | - (u16::try_from(p_t.len()).expect("redex term text is greater than 2^16 characters") - 1) / 2, 102 | (self.height - u16::try_from(*index).expect("redex terms longer than 2^16 terms")).saturating_sub(1)) 103 | }, 104 | | State::AtLeft => (self.width / 4 - 1, self.height), 105 | | State::InRight(index) => { 106 | let term = guard.reduction.get(*index).expect("bounds check failed"); 107 | let r_t = f(term); 108 | (3 * self.width / 4 109 | - (u16::try_from(r_t.len()).expect("reduction term text is greater than 2^16 \ 110 | characters") - 1) 111 | / 2 - 1, 112 | (self.height - u16::try_from(*index).expect("reduction terms longer than 2^16 terms")).saturating_sub(1)) 113 | }, 114 | | State::AtRight => (3 * self.width / 4 - 1, self.height), 115 | | State::Editing(ref s, _) => 116 | (self.width / 2 117 | + (u16::try_from(s.len()).expect("input field text is greater than 2^16 characters") + 1) 118 | / 2, 119 | 0), 120 | } 121 | } 122 | 123 | fn display_status(&mut self, state:&State) { 124 | let status = match &state { 125 | | State::Editing(msg, _) => msg.clone(), 126 | | _ => self.default_status.clone(), 127 | }; 128 | let width = usize::from(self.width); 129 | print!("{0: ^1$}\r\n", status, width); 130 | print!("-{0:-^1$}-{2:-^3$}-\r\n", 131 | self.labels.0, 132 | self.left_half_width(1).into(), 133 | self.labels.1, 134 | self.right_half_width(1).into()); 135 | } 136 | 137 | async fn display String+Copy>(&mut self, f:F, state:State) -> io::Result<()> { 138 | self.display_status(&state); 139 | for row in 2..self.height - 1 { 140 | let mut s = "|".to_owned(); 141 | Self::queue(terminal::Clear(ClearType::CurrentLine))?; 142 | self.make_left_half(f, row, &mut s).await; 143 | s.push('|'); 144 | self.make_right_half(f, row, &mut s).await; 145 | s.push('|'); 146 | println!("{}\r", s); 147 | } 148 | let mut s = "|".to_owned(); 149 | self.make_left_half(f, self.height, &mut s).await; 150 | s.push('|'); 151 | self.make_right_half(f, self.height, &mut s).await; 152 | s.push('|'); 153 | print!("{}", s); 154 | Ok(()) 155 | } 156 | 157 | pub async fn refresh_screen String+Copy>(&mut self, f:F, state:&State, 158 | should_quit:bool) 159 | -> io::Result<()> { 160 | Self::queue(cursor::Hide)?; 161 | Self::queue(cursor::MoveTo(0, 0))?; 162 | if should_quit { 163 | Self::clear_screen()?; 164 | println!("Goodbye.\r"); 165 | } else { 166 | self.display(f, state.clone()).await?; 167 | let (x, y) = self.get_target(f, state).await; 168 | Self::queue(cursor::MoveTo(x, y))?; 169 | } 170 | Self::queue(cursor::Show)?; 171 | Self::flush() 172 | } 173 | 174 | pub async fn read(&'_ self) -> RwLockReadGuard<'_, Rule> { self.sides.read().await } 175 | 176 | pub async fn write(&'_ self) -> RwLockWriteGuard<'_, Rule> { self.sides.write().await } 177 | } 178 | --------------------------------------------------------------------------------