From 8079404e8c7c8a9e6e13df344eb9b7b0e0613b08 Mon Sep 17 00:00:00 2001 From: "James [Undefined]" Date: Wed, 6 Oct 2021 18:42:31 -0500 Subject: [PATCH] UI improvements for structural editor (#32) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use std::fmt for centering, add labels for halves This makes everything cleaner! With some algebra and codeā„¢, it's possible to use `format!` for centering text. This commit also adds labels to the pattern and replacement half, just being centered boxes `| Pattern |` and `| Replacement |` in the usually bland `--------`(...) line after the status. * Fix rule index (off-by-one errors go brrr) * Switch from termion to crossterm Squash of: - Switch from termion to crossterm (@brightly-salty) - Abstract away a view for easier layouting (@brightly-salty) - Add lockfile (@ThePuzzlemaker) - Undo some AST/parser changes (@ThePuzzlemaker) Co-authored-by: ThePuzzlemaker * Fix code for new AST, make pretty_rule use &[Term] instead of Vec * Rebase and fix * Add failing interactive mode * Improve interactive rewriter * Fix prelude + mlatu.pl * Add CLI + standardize keybindings Co-authored-by: Caden Haustein --- .gitignore | 2 - Cargo.lock | 760 ++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 17 +- mlatu.pl | 2 + prelude.mlt | 12 - rust-toolchain.toml | 2 +- src/ast.rs | 25 +- src/editor.rs | 509 +++++++++++++++-------------- src/interactive.rs | 220 +++++++++++++ src/lib.rs | 8 +- src/main.rs | 152 ++++----- src/prolog.rs | 49 +++ src/terminal.rs | 46 --- src/view.rs | 173 ++++++++++ 14 files changed, 1581 insertions(+), 396 deletions(-) create mode 100644 Cargo.lock create mode 100644 src/interactive.rs delete mode 100644 src/terminal.rs create mode 100644 src/view.rs diff --git a/.gitignore b/.gitignore index 1e68398..393b2d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ debug/ target/ -Cargo.lock - **/*.rs.bk .pdb \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..48b63f1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,760 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bindgen" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453c49e5950bb0eb63bb3df640e31618846c89d5b7faa54040d76e98e0134375" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cexpr" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db507a7679252d2276ed0dd8113c6875ec56d3089f9225b2b42c30cc1f8e5c89" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10612c0ec0e0a1ff0e97980647cb058a6e7aedb913d01d009c406b8b7d0b26ee" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "combine" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a909e4d93292cd8e9c42e189f61681eff9d67b6541f96b8a1a737f23737bd001" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "crossterm" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486d44227f71a1ef39554c0dc47e44b9f4139927c75043312690c3f476d1d788" +dependencies = [ + "bitflags", + "crossterm_winapi", + "futures-core", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" +dependencies = [ + "winapi", +] + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + +[[package]] +name = "futures-core" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "instant" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" + +[[package]] +name = "libloading" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "lock_api" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "mio" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "mlatu" +version = "0.1.0" +dependencies = [ + "clap", + "combine", + "crossterm", + "swipl", + "swipl-info", + "tokio", + "tokio-stream", + "unic-ucd-category", +] + +[[package]] +name = "nom" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +dependencies = [ + "bitvec", + "funty", + "memchr", + "version_check", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "pin-project-lite" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "signal-hook" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "swipl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643507ed49b904015ac166c579664a20346e655adc7980c7cbc260ef6cec8945" +dependencies = [ + "lazy_static", + "swipl-fli", + "swipl-macros", + "thiserror", +] + +[[package]] +name = "swipl-fli" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faae0c7a45e3969f07432778774130bcb730f639f888cd8ee96ddd8dd64e5b9e" +dependencies = [ + "bindgen", + "swipl-info", +] + +[[package]] +name = "swipl-info" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b7cf6cbfa5484d916d67858e629874fcc76136aee299d184e81b9751c74828" +dependencies = [ + "regex", +] + +[[package]] +name = "swipl-macros" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a575fc11dc12e551e13b1c8e208a0e054bda3bfaeeb1960ecc69bb7847b610a0" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc" +dependencies = [ + "autocfg", + "bytes", + "memchr", + "num_cpus", + "parking_lot", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "154794c8f499c2619acd19e839294703e9e32e7630ef5f46ea80d4ef0fbee5eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-category" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8d4591f5fcfe1bd4453baaf803c40e1b1e69ff8455c47620440b46efef91c0" +dependencies = [ + "matches", + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "which" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +dependencies = [ + "libc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" diff --git a/Cargo.toml b/Cargo.toml index 9832fff..9207104 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,13 +17,22 @@ build = "build.rs" [dependencies] combine = "4.6.1" -termion = "1.5.6" +crossterm = { version = "0.21.0", features = ["event-stream"] } unic-ucd-category = "0.9.0" swipl = "0.3.5" +tokio-stream = "0.1.7" +clap = "2.33.3" -[dependencies.async-std] -version = "1.10.0" -features = ["unstable", "attributes"] +[dependencies.tokio] +version = "1.12.0" +features = [ + "rt-multi-thread", + "io-util", + "macros", + "sync", + "fs", + "parking_lot" +] [build-dependencies] swipl-info = "0.3.1" diff --git a/mlatu.pl b/mlatu.pl index 6f0a160..c3a7a0b 100644 --- a/mlatu.pl +++ b/mlatu.pl @@ -12,6 +12,7 @@ equiv(Xs, Other). builtin_equiv(['u',Quote|Xs], Other) :- + is_list(Quote), append(Quote, Xs, NewXs), equiv(NewXs, Other). @@ -19,6 +20,7 @@ equiv([[X]|Xs], Other). builtin_equiv(['c',Quote1,Quote2|Xs], Other) :- + is_list(Quote1), is_list(Quote2), append(Quote1, Quote2, NewQuote), equiv([NewQuote|Xs], Other). diff --git a/prelude.mlt b/prelude.mlt index 22d48e8..e69de29 100644 --- a/prelude.mlt +++ b/prelude.mlt @@ -1,12 +0,0 @@ -concat = c; -unquote = u; -quote = q; -swap = s; -remove = r; -dup = d; - -prepend = swap quote swap concat; -p = prepend; - -unquote-before = swap quote cat unquote; -uqb = unquote-before; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 15bf2e9..f2f90c8 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2021-09-27" +channel = "nightly-2021-10-06" components = ["rustfmt", "rust-std", "rustc", "cargo", "rust-docs", "clippy"] \ No newline at end of file diff --git a/src/ast.rs b/src/ast.rs index 039aacd..86392b9 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,6 +1,5 @@ use std::fmt; - -use async_std::sync::Arc; +use std::sync::Arc; #[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)] pub enum Term { @@ -34,7 +33,7 @@ impl fmt::Display for Term { pub type Rule = (Vec, Vec); -pub fn pretty_rule(pattern:Vec, replacement:Vec, s:&mut String) { +pub fn pretty_rule(pattern:&[Term], replacement:&[Term], s:&mut String) { for term in pattern { s.push_str(&term.to_string()); s.push(' '); @@ -45,3 +44,23 @@ pub fn pretty_rule(pattern:Vec, replacement:Vec, s:&mut String) { } s.push(';'); } + +mod prolog { + use super::Term as AstTerm; + use crate::prolog::{attempt_opt, term_getable, PrologError, Term, TermGetable}; + + term_getable! { + (AstTerm, term) => { + match term.get::>() { + Ok(terms) => { + let quote = terms.into_iter().rev().collect(); + Some(Self::new_quote(quote)) + }, + Err(PrologError::Exception) => None, + Err(PrologError::Failure) => { + attempt_opt(term.get_atom_name(|x| x.map(Self::new_word))).unwrap_or(None).flatten() + } + } + } + } +} diff --git a/src/editor.rs b/src/editor.rs index 60b8690..452dacb 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -1,38 +1,27 @@ -use async_std::fs::File; -use async_std::io; -use async_std::prelude::*; -use termion::event::Key; +use std::sync::Arc; -use crate::ast::{Rule, Term}; -use crate::parser::parse_terms; -use crate::terminal::Terminal; - -const DEFAULT_STATUS:&str = "Welcome to the mlatu editor"; - -#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] -pub enum Location { - Pattern, - Replacement, -} +use tokio::fs::File; +use tokio::io; +use tokio::io::{AsyncSeekExt, AsyncWriteExt}; +use tokio::sync::RwLock; -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] -pub enum State { - InLoc(Location, usize), - AtLoc(Location), - Editing(String, Box), -} +use crate::ast::{Rule, Term}; +use crate::parser::parse_term; +use crate::pretty_rule; +use crate::view::{State, View}; pub struct Editor { file:File, - terminal:Terminal, - rules:Vec, + view:View, + rules:Vec>>, rule_idx:usize, should_quit:bool, state:State, } fn die(e:&std::io::Error) { - Terminal::clear_screen(); + let _result = crossterm::terminal::Clear(crossterm::terminal::ClearType::All); + let _result = crossterm::terminal::disable_raw_mode(); panic!("{}", e) } @@ -40,280 +29,324 @@ impl Editor { /// # Errors /// /// Will return `Err` if there was an error constructing a terminal - pub fn new(file:File, rules:Vec) -> io::Result { - let terminal = Terminal::new()?; + pub fn new(file:File, original_rules:Vec) -> io::Result { + crossterm::terminal::enable_raw_mode()?; let should_quit = false; let rule_idx = 0; - let (rules, state) = if rules.is_empty() { - (vec![Rule::new(vec![], vec![])], State::AtLoc(Location::Pattern)) + let (rules, state) = if original_rules.is_empty() { + (vec![Arc::new(RwLock::new((vec![], vec![])))], State::AtLeft) } else { - (rules, State::InLoc(Location::Pattern, 0)) + let mut rules = Vec::new(); + for rule in original_rules { + rules.push(Arc::new(RwLock::new(rule))); + } + (rules, State::InLeft(0)) }; - Ok(Self { file, terminal, rules, rule_idx, should_quit, state }) + let default_status = format!("mlatu editor (rule {}/{})", rule_idx + 1, rules.len()); + let view = View::new(Arc::clone(&rules[rule_idx]), + ("| Pattern |".to_string(), "| Replacement |".to_string()), + default_status)?; + Ok(Self { file, view, rules, rule_idx, should_quit, state }) } pub async fn run(&mut self) { - if let Err(error) = self.refresh_screen().await { - die(&error); - } loop { - if let Err(error) = self.process_keypress().await { - die(&error); - } - if let Err(error) = self.refresh_screen().await { + if let Err(error) = self.view.refresh_screen(&self.state, self.should_quit).await { die(&error); } if self.should_quit { + let _result = crossterm::terminal::disable_raw_mode(); break } + if let Err(error) = self.process_keypress().await { + die(&error); + } } } - fn make_pattern_half(&self, row:u16, s:&mut String) { - let width = usize::from(self.terminal.width()); - Terminal::clear_current_line(); - if let Some(term) = self.rules[self.rule_idx].pattern.get(usize::from(row) - 2) { - let p_t = term.to_string(); - s.push_str(&" ".repeat(width / 4 - p_t.len() / 2 - s.len() - 1)); - s.push_str(&p_t); + async fn save(&mut self) -> io::Result<()> { + self.file.seek(std::io::SeekFrom::Start(0)).await?; + for rule in &self.rules { + let guard = rule.read().await; + let mut rule = String::new(); + pretty_rule(&*guard.0, &*guard.1, &mut rule); + rule.push('\n'); + self.file.write_all(rule.as_bytes()).await?; } - s.push_str(&" ".repeat(width / 2 - s.len())); + Ok(()) } - fn make_replacement_half(&self, row:u16, s:&mut String) { - let width = usize::from(self.terminal.width()); - if let Some(term) = self.rules[self.rule_idx].replacement.get(usize::from(row) - 2) { - let r_t = term.to_string(); - s.push_str(&" ".repeat(3 * width / 4 - r_t.len() / 2 - s.len() - 1)); - s.push_str(&r_t); - } - s.push_str(&" ".repeat(width - s.len() - 1)); + async fn set_left_view(&mut self, index:usize) -> io::Result<()> { + let rule = &self.rules[self.rule_idx]; + let default_status = format!("mlatu editor (rule {}/{})", self.rule_idx + 1, self.rules.len()); + let guard = rule.read().await; + self.state = + if guard.0.is_empty() { State::AtLeft } else { State::InLeft(index.min(guard.0.len() - 1)) }; + self.view = View::new(Arc::clone(rule), + ("| Pattern |".to_string(), "| Replacement |".to_string()), + default_status)?; + Ok(()) + } + + async fn set_right_view(&mut self, index:usize) -> io::Result<()> { + let rule = &self.rules[self.rule_idx]; + let default_status = format!("mlatu editor (rule {}/{})", self.rule_idx + 1, self.rules.len()); + let guard = rule.read().await; + self.state = if guard.1.is_empty() { + State::AtRight + } else { + State::InRight(index.min(guard.1.len() - 1)) + }; + self.view = View::new(Arc::clone(rule), + ("| Pattern |".to_string(), "| Replacement |".to_string()), + default_status)?; + Ok(()) } - fn go_to_position(&self) { - let width = self.terminal.width(); - match &self.state { - | State::InLoc(Location::Pattern, index) => { - let term = self.rules[self.rule_idx].pattern.get(*index).expect("bounds check failed"); - let p_t = term.to_string(); - Terminal::cursor_position(width / 4 - - u16::try_from(p_t.len()).expect("pattern term text is \ - greater than 2^16 \ - characters") - / 2 - - 1, - u16::try_from(*index).expect("pattern terms longer than 2^16 \ - terms") - + 2); + async fn process_left(&mut self) -> io::Result<()> { + match self.state { + | State::AtRight => { + let guard = self.view.read().await; + self.state = if guard.0.is_empty() { State::AtLeft } else { State::InLeft(0) }; }, - | State::AtLoc(Location::Pattern) => { - Terminal::cursor_position(width / 4 - 1, 2); + | State::InRight(index) => { + let guard = self.view.read().await; + self.state = if guard.0.is_empty() { + State::AtLeft + } else { + State::InLeft(index.min(guard.0.len() - 1)) + } }, - | State::InLoc(Location::Replacement, index) => { - let term = self.rules[self.rule_idx].replacement.get(*index).expect("bounds check failed"); - let r_t = term.to_string(); - Terminal::cursor_position(3 * width / 4 - - (u16::try_from(r_t.len()).expect("replacement term text is \ - greater than 2^16 \ - characters")) - / 2 - - 1, - u16::try_from(*index).expect("replacement terms longer than \ - 2^16 terms") - + 2); + | State::AtLeft if self.rule_idx > 0 => { + self.rule_idx -= 1; + self.set_right_view(0).await?; }, - | State::AtLoc(Location::Replacement) => { - Terminal::cursor_position(3 * width / 4 - 1, 2); + | State::InLeft(index) if self.rule_idx > 0 => { + self.rule_idx -= 1; + self.set_right_view(index).await?; }, - | State::Editing(s, _) => - Terminal::cursor_position(width / 2 - + (u16::try_from(s.len()).expect("input field text is greater \ - than 2^16 characters") - + 1) - / 2, - 0), - } + | _ => {}, + }; + Ok(()) } - fn display_status(&self) { - let status = match &self.state { - | State::Editing(msg, _) => msg.clone(), - | _ => format!("{} (rule {}/{})", DEFAULT_STATUS, self.rule_idx, self.rules.len()), + async fn process_right(&mut self) -> io::Result<()> { + match self.state { + | State::AtLeft => { + let guard = self.view.read().await; + self.state = if guard.1.is_empty() { State::AtRight } else { State::InRight(0) }; + }, + | State::InLeft(index) => { + let guard = self.view.read().await; + self.state = if guard.1.is_empty() { + State::AtRight + } else { + State::InRight(index.min(guard.1.len() - 1)) + } + }, + | State::AtRight if self.rule_idx < (self.rules.len() - 1) => { + self.rule_idx += 1; + self.set_left_view(0).await?; + }, + | State::InRight(index) if self.rule_idx < (self.rules.len() - 1) => { + self.rule_idx += 1; + self.set_left_view(index).await?; + }, + | _ => {}, }; - let width = usize::from(self.terminal.width()); - let mut s = String::new(); - s.push_str(&" ".repeat(width / 2 - status.len() / 2)); - s.push_str(&status); - s.push_str(&" ".repeat(width - s.len())); - println!("{}\r", s); - println!("{}\r", "-".repeat(width)); + Ok(()) } - fn display(&self) { - self.display_status(); - let height = self.terminal.height(); - for row in 2..height - 1 { - let mut s = "|".to_owned(); - self.make_pattern_half(row, &mut s); - s.push('|'); - self.make_replacement_half(row, &mut s); - s.push('|'); - println!("{}\r", s); + async fn input_word(&mut self, s:String, state:Box) { + if let Ok(term) = parse_term(&s) { + let mut guard = self.view.write().await; + + match *state { + | State::AtLeft => { + guard.0 = vec![term]; + self.state = State::InLeft(0); + }, + | State::AtRight => { + guard.1 = vec![term]; + self.state = State::InRight(0); + }, + | State::InLeft(index) => { + guard.0.insert(index + 1, term); + self.state = State::InLeft(index + 1); + }, + | State::InRight(index) => { + guard.1.insert(index + 1, term); + self.state = State::InRight(index + 1); + }, + | _ => {}, + } } - let mut s = "|".to_owned(); - self.make_pattern_half(height, &mut s); - s.push('|'); - self.make_replacement_half(height, &mut s); - s.push('|'); - print!("{}", s); } - async fn refresh_screen(&self) -> io::Result<()> { - Terminal::cursor_hide(); - Terminal::cursor_position(0, 0); - if self.should_quit { - Terminal::clear_screen(); - println!("Goodbye.\r"); - } else { - self.display(); - self.go_to_position(); + async fn concat_left(&mut self, index:usize) { + let mut guard = self.view.write().await; + if let Term::Quote(mut terms) = guard.0[index - 1].clone() { + if let Term::Quote(other_terms) = guard.0[index].clone() { + terms.extend(other_terms); + guard.0.remove(index); + guard.0[index] = Term::new_quote(terms); + } } - Terminal::cursor_show(); - Terminal::flush().await } - fn get_loc(&self, loc:Location) -> &Vec { - match loc { - | Location::Pattern => &self.rules[self.rule_idx].pattern, - | Location::Replacement => &self.rules[self.rule_idx].replacement, + async fn concat_right(&mut self, index:usize) { + let mut guard = self.view.write().await; + if let Term::Quote(mut terms) = guard.1[index - 1].clone() { + if let Term::Quote(other_terms) = guard.1[index].clone() { + terms.extend(other_terms); + guard.1.remove(index); + guard.1[index] = Term::new_quote(terms); + } } } - fn get_loc_mut(&mut self, loc:Location) -> &mut Vec { - match loc { - | Location::Pattern => &mut self.rules[self.rule_idx].pattern, - | Location::Replacement => &mut self.rules[self.rule_idx].replacement, + async fn unquote_left(&mut self, index:usize) { + let mut guard = self.view.write().await; + if let Term::Quote(terms) = guard.0[index].clone() { + let mut index = index; + guard.0.remove(index); + for term in terms { + guard.0.insert(index, term); + index += 1; + } + self.state = if guard.0.is_empty() { + State::AtLeft + } else { + State::InLeft(index.saturating_sub(1).min(guard.0.len() - 1)) + }; } } - fn set_position(&mut self, loc:Location, index:usize) { - self.state = if self.get_loc(loc).is_empty() { - State::AtLoc(loc) - } else { - State::InLoc(loc, index.min(self.get_loc(loc).len() - 1)) + async fn unquote_right(&mut self, index:usize) { + let mut guard = self.view.write().await; + if let Term::Quote(terms) = guard.1[index].clone() { + let mut index = index; + guard.1.remove(index); + for term in terms { + guard.1.insert(index, term); + index += 1; + } + self.state = if guard.1.is_empty() { + State::AtRight + } else { + State::InRight(index.saturating_sub(1).min(guard.1.len() - 1)) + }; } } - async fn save(&mut self) -> io::Result<()> { - self.file.seek(std::io::SeekFrom::Start(0)).await?; - for rule in &self.rules { - let mut rule = rule.to_string(); - rule.push('\n'); - self.file.write_all(rule.as_bytes()).await?; - } - Ok(()) + async fn remove_left(&mut self, index:usize) { + let mut guard = self.view.write().await; + guard.0.remove(index); + self.state = + if guard.0.is_empty() { State::AtLeft } else { State::InLeft(index.min(guard.0.len() - 1)) }; } - fn submit_input(&mut self, s:&str, state:&State) { - if let Ok(terms) = parse_terms(s) { - match state { - | State::AtLoc(loc) => { - *self.get_loc_mut(*loc) = terms; - self.set_position(*loc, 0); - }, - | State::InLoc(loc, index) => { - let mut index = *index; - let mut_terms = self.get_loc_mut(*loc); - for term in terms { - mut_terms.insert(index, term); - index += 1; - } - self.set_position(*loc, index - 1); - }, - | State::Editing(..) => {}, - } - } + async fn remove_right(&mut self, index:usize) { + let mut guard = self.view.write().await; + guard.1.remove(index); + self.state = if guard.1.is_empty() { + State::AtRight + } else { + State::InRight(index.min(guard.1.len() - 1)) + }; } async fn process_keypress(&mut self) -> io::Result<()> { - let pressed_key = Terminal::read_key()?; - match (pressed_key, self.state.clone()) { - | (Key::Esc, _) => self.should_quit = true, - | (Key::Ctrl('s'), _) => self.save().await?, - | (Key::Ctrl('d'), _) => { + use crossterm::event::KeyCode::{Backspace, Char, Delete, Down, Esc, Left, Right, Up}; + use crossterm::event::KeyModifiers; + + let event = self.view.read_key().await?; + match (event.code, event.modifiers) { + | (Esc, _) => self.should_quit = true, + | (Char('w'), KeyModifiers::CONTROL) => self.save().await?, + | (Char('r'), KeyModifiers::CONTROL) => { if self.rules.len() == 1 { - self.rules[0].pattern = Vec::new(); - self.rules[0].replacement = Vec::new(); + self.rules[0] = Arc::new(RwLock::new((Vec::new(), Vec::new()))); } else { self.rules.remove(self.rule_idx); self.rule_idx = self.rule_idx.min(self.rules.len() - 1); } - self.state = State::AtLoc(Location::Pattern); - }, - | (Key::Ctrl(' '), _) => { - self.rules.insert(self.rule_idx, Rule::new(vec![], vec![])); - self.state = State::AtLoc(Location::Pattern); + self.set_left_view(0).await?; }, - | (Key::Backspace | Key::Delete, State::Editing(s, state)) => { - let mut s = s; - s.pop(); - self.state = State::Editing(s, state); - }, - | (Key::Char('\n'), State::Editing(s, state)) => self.submit_input(&s, &*state), - | (Key::Char(c), State::Editing(s, state)) => { - let mut s = s; - s.push(c); - self.state = State::Editing(s, state); - }, - | (Key::Left, State::AtLoc(Location::Replacement)) => self.set_position(Location::Pattern, 0), - | (Key::Left, State::InLoc(Location::Replacement, index)) => - self.set_position(Location::Pattern, index), - | (Key::Left, State::AtLoc(Location::Pattern)) if self.rule_idx > 0 => { - self.rule_idx -= 1; - self.set_position(Location::Replacement, 0); + | (Char(' '), KeyModifiers::CONTROL) => { + self.rules.insert(self.rule_idx, Arc::new(RwLock::new((vec![], vec![])))); + self.set_left_view(0).await?; }, - | (Key::Left, State::InLoc(Location::Pattern, index)) if self.rule_idx > 0 => { - self.rule_idx -= 1; - self.set_position(Location::Replacement, index); + | (Char(c), _) => match (c, self.state.clone()) { + | (' ', State::Editing(s, state)) => self.input_word(s, state).await, + | (c, State::Editing(s, state)) => { + let mut s = s; + s.push(c); + self.state = State::Editing(s, state); + }, + | ('q', State::InLeft(index)) => { + let mut guard = self.view.write().await; + guard.0[index] = Term::new_quote(vec![guard.0[index].clone()]); + }, + | ('q', State::InRight(index)) => { + let mut guard = self.view.write().await; + guard.1[index] = Term::new_quote(vec![guard.1[index].clone()]); + }, + | ('r', State::InLeft(index)) => self.remove_left(index).await, + | ('r', State::InRight(index)) => self.remove_right(index).await, + | ('d', State::InLeft(index)) => { + let mut guard = self.view.write().await; + let term = guard.0[index].clone(); + guard.0.insert(index, term); + self.state = State::InLeft(index.min(guard.0.len() - 1)); + }, + | ('d', State::InRight(index)) => { + let mut guard = self.view.write().await; + let term = guard.1[index].clone(); + guard.1.insert(index, term); + self.state = State::InRight(index.min(guard.1.len() - 1)); + }, + | ('s', State::InLeft(index)) if index > 0 => { + let mut guard = self.view.write().await; + guard.0.swap(index, index - 1); + }, + | ('s', State::InRight(index)) if index > 0 => { + let mut guard = self.view.write().await; + guard.1.swap(index, index - 1); + }, + | ('c', State::InLeft(index)) if index > 0 => self.concat_left(index).await, + | ('c', State::InRight(index)) if index > 0 => self.concat_right(index).await, + | ('u', State::InLeft(index)) => self.unquote_left(index).await, + | ('u', State::InRight(index)) => self.unquote_right(index).await, + | (' ', _) => self.state = State::Editing(String::new(), Box::new(self.state.clone())), + + | _ => {}, }, - | (Key::Right, State::AtLoc(Location::Pattern)) => - self.set_position(Location::Replacement, 0), - | (Key::Right, State::InLoc(Location::Pattern, index)) => - self.set_position(Location::Replacement, index), - | (Key::Right, State::AtLoc(Location::Replacement)) - if self.rule_idx < (self.rules.len() - 1) => - { - self.rule_idx += 1; - self.set_position(Location::Pattern, 0); - } - | (Key::Right, State::InLoc(Location::Replacement, index)) - if self.rule_idx < (self.rules.len() - 1) => - { - self.rule_idx += 1; - self.set_position(Location::Pattern, index); - } - | (Key::Up, State::InLoc(loc, index)) => self.set_position(loc, index.saturating_sub(1)), - | (Key::Down, State::InLoc(loc, index)) => self.set_position(loc, index + 1), - | (Key::Delete | Key::Backspace, State::InLoc(loc, index)) => { - (*self.get_loc_mut(loc)).remove(index); - self.set_position(loc, index); + | (Backspace | Delete, _) => match self.state.clone() { + | State::Editing(s, state) => { + let mut s = s; + s.pop(); + self.state = State::Editing(s, state); + }, + | _ => {}, }, - | (Key::Char(' '), state) => self.state = State::Editing(String::new(), Box::new(state)), - | (Key::Char('q'), State::InLoc(loc, index)) => { - let term = self.get_loc(loc)[index].clone(); - (*self.get_loc_mut(loc))[index] = Term::new_quote(vec![term]); + | (Left, _) => self.process_left().await?, + | (Right, _) => self.process_right().await?, + | (Up, _) => match self.state { + | State::InLeft(index) => self.state = State::InLeft(index.saturating_sub(1)), + | State::InRight(index) => self.state = State::InRight(index.saturating_sub(1)), + | _ => {}, }, - | (Key::Char('i'), State::InLoc(loc, index)) => { - if let Term::Quote(terms) = self.get_loc(loc)[index].clone() { - let mut index = index; - let mut_terms = self.get_loc_mut(loc); - mut_terms.remove(index); - for term in terms { - mut_terms.insert(index, term); - index += 1; - } - self.set_position(loc, index.saturating_sub(1)); - } + | (Down, _) => match self.state { + | State::InLeft(index) => { + let guard = self.view.read().await; + self.state = State::InLeft((index + 1).min(guard.0.len() - 1)); + }, + | State::InRight(index) => { + let guard = self.view.read().await; + self.state = State::InRight((index + 1).min(guard.1.len() - 1)); + }, + | _ => {}, }, | _ => {}, } diff --git a/src/interactive.rs b/src/interactive.rs new file mode 100644 index 0000000..c56ac49 --- /dev/null +++ b/src/interactive.rs @@ -0,0 +1,220 @@ +use std::io; +use std::sync::Arc; + +use tokio::sync::mpsc::error::TryRecvError; +use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; +use tokio::sync::RwLock; + +use crate::ast::Term; +use crate::parser::parse_term; +use crate::view::{State, View}; + +pub struct Interactive { + view:View, + should_quit:bool, + state:State, + tx:UnboundedSender>, +} + +fn die(e:&io::Error) -> ! { + let _result = crossterm::terminal::Clear(crossterm::terminal::ClearType::All); + let _result = crossterm::terminal::disable_raw_mode(); + panic!("{}", e) +} + +impl Interactive { + /// # Errors + /// + /// Will return `Err` if there was an error constructing a terminal + pub fn new(tx:UnboundedSender>) -> io::Result { + crossterm::terminal::enable_raw_mode()?; + let should_quit = false; + let rule = Arc::new(RwLock::new((vec![], vec![]))); + let state = State::AtLeft; + let default_status = "mlatu interface".to_string(); + let view = View::new(Arc::clone(&rule), + ("| Input |".to_string(), "| Output |".to_string()), + default_status)?; + Ok(Self { view, should_quit, state, tx }) + } + + async fn handle_prolog(rx:&mut UnboundedReceiver, String>>) + -> Option> { + match rx.try_recv() { + | Ok(Ok(terms)) => Some(terms), + | Ok(Err(_error)) => { + // TODO: proper error reporting here, dying is painful + // die(&io::Error::new(io::ErrorKind::Other, error)); + None + }, + | Err(TryRecvError::Empty) => None, + | Err(TryRecvError::Disconnected) => die(&io::Error::new(io::ErrorKind::Other, + "prolog handler thread \ + unexpectedly disconnected")), + } + } + + /// # Panics + /// + /// Panics if there was an error displaying to the screen, an error processing + /// keypresses, or an error handling the Prolog interface. + pub async fn run(&mut self, rx:&mut UnboundedReceiver, String>>) { + loop { + Self::handle_prolog(rx).await; + if let Err(error) = self.view.refresh_screen(&self.state, self.should_quit).await { + die(&error); + } + if self.should_quit { + let _result = crossterm::terminal::disable_raw_mode(); + break + } + + tokio::select! { + res = self.process_keypress() => { + if let Err(error) = res { + die(&error); + } + } + res = Self::handle_prolog(rx) => { + if let Some(terms) = res { + let mut guard = self.view.write().await; + guard.1 = terms; + } + } + }; + } + } + + async fn remove(&mut self, index:usize) { + let mut guard = self.view.write().await; + guard.0.remove(index); + self.state = + if guard.0.is_empty() { State::AtLeft } else { State::InLeft(index.min(guard.0.len() - 1)) }; + self.tx.send(guard.0.clone()).expect("send terms"); + } + + async fn quote(&mut self, index:usize) { + let mut guard = self.view.write().await; + guard.0[index] = Term::new_quote(vec![guard.0[index].clone()]); + self.tx.send(guard.0.clone()).expect("send terms"); + } + + async fn swap(&mut self, index:usize) { + let mut guard = self.view.write().await; + guard.0.swap(index, index - 1); + self.tx.send(guard.0.clone()).expect("send terms"); + } + + async fn dup(&mut self, index:usize) { + let mut guard = self.view.write().await; + let term = guard.0[index].clone(); + guard.0.insert(index, term); + self.tx.send(guard.0.clone()).expect("send terms"); + } + + async fn concat(&mut self, index:usize) { + let mut guard = self.view.write().await; + if let Term::Quote(mut terms) = guard.0[index - 1].clone() { + if let Term::Quote(other_terms) = guard.0[index].clone() { + terms.extend(other_terms); + guard.0.remove(index); + guard.0[index] = Term::new_quote(terms); + self.tx.send(guard.0.clone()).expect("send terms"); + } + } + } + + async fn unquote(&mut self, index:usize) { + let mut guard = self.view.write().await; + if let Term::Quote(terms) = guard.0[index].clone() { + let mut index = index; + guard.0.remove(index); + for term in terms { + guard.0.insert(index, term); + index += 1; + } + self.state = if guard.0.is_empty() { + State::AtLeft + } else { + State::InLeft(index.saturating_sub(1).min(guard.0.len() - 1)) + }; + self.tx.send(guard.0.clone()).expect("send terms"); + } + } + + async fn process_keypress(&mut self) -> io::Result<()> { + use crossterm::event::KeyCode::{Backspace, Char, Delete, Down, Esc, Up}; + + let event = self.view.read_key().await?; + match (event.code, event.modifiers) { + | (Esc, _) => self.should_quit = true, + | (Char(c), _) => match (c, self.state.clone()) { + | (' ', State::Editing(s, state)) => + if let Ok(term) = parse_term(&s) { + let mut guard = self.view.write().await; + match *state { + | State::AtLeft => { + guard.0 = vec![term]; + self.state = State::InLeft(0); + self.tx.send(guard.0.clone()).expect("send terms"); + }, + | State::InLeft(index) => { + guard.0.insert(index + 1, term); + self.state = State::InLeft(index + 1); + self.tx.send(guard.0.clone()).expect("send terms"); + }, + | _ => {}, + } + }, + | (c, State::Editing(s, state)) => { + let mut s = s; + s.push(c); + self.state = State::Editing(s, state); + }, + | ('r', State::InLeft(index)) => self.remove(index).await, + | ('q', State::InLeft(index)) => self.quote(index).await, + | ('s', State::InLeft(index)) if index > 0 => self.swap(index).await, + | ('d', State::InLeft(index)) => self.dup(index).await, + | ('c', State::InLeft(index)) if index > 0 => self.concat(index).await, + | ('u', State::InLeft(index)) => self.unquote(index).await, + | (' ', State::InLeft(index)) => + self.state = State::Editing(String::new(), Box::new(State::InLeft(index))), + | (' ', State::AtLeft) => + self.state = State::Editing(String::new(), Box::new(State::AtLeft)), + | _ => {}, + }, + | (Backspace | Delete, _) => match self.state.clone() { + | State::Editing(s, state) => { + let mut s = s; + s.pop(); + self.state = State::Editing(s, state); + }, + | _ => {}, + }, + | (Up, _) => match self.state { + | State::InLeft(index) => { + let guard = self.view.read().await; + self.state = if guard.0.is_empty() { + State::AtLeft + } else { + State::InLeft(index.saturating_sub(1).min(guard.0.len() - 1)) + }; + }, + | _ => {}, + }, + | (Down, _) => match self.state { + | State::InLeft(index) => { + let guard = self.view.read().await; + self.state = if guard.0.is_empty() { + State::AtLeft + } else { + State::InLeft((index + 1).min(guard.0.len() - 1)) + }; + }, + | _ => {}, + }, + | _ => {}, + } + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 34f8383..cfbfcdc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,11 +24,13 @@ clippy::verbose_file_reads)] mod ast; -// mod editor; +mod editor; +mod interactive; mod parser; pub mod prolog; -// mod terminal; +mod view; pub use ast::{pretty_rule, Rule, Term}; -// pub use editor::Editor; +pub use editor::Editor; +pub use interactive::Interactive; pub use parser::{parse_rule, parse_rules, parse_term, parse_terms}; diff --git a/src/main.rs b/src/main.rs index e2e19f7..3bdf6fb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,96 +1,74 @@ -use async_std::fs::OpenOptions; -use async_std::io; -use async_std::io::prelude::*; -use async_std::path::PathBuf; -use mlatu::{parse_rules, parse_terms, prolog}; -use prolog::util::AssertLocation; -use prolog::{codegen, ContextExt}; +use clap::{App, Arg, SubCommand}; +use mlatu::prolog::codegen; +use mlatu::prolog::util::{AssertLocation, ContextExt}; +use mlatu::{parse_rules, prolog, Editor, Interactive, Rule}; +use tokio::fs::{read_to_string, File}; +use tokio::sync::mpsc::unbounded_channel; -// TODO: actual CLI, better REPL (w/ rustyline) -// TODO: Bring back structural editing -// BUG: Ctrl-C forwards to SWIPl -#[async_std::main] -async fn main() -> std::io::Result<()> { - let engine = mlatu::prolog::init_engine(); - let ctx:prolog::Context<'_, _> = engine.activate().into(); - - let path = if let Some(filename) = std::env::args().nth(2) { - PathBuf::from(filename) - } else { - PathBuf::from("./prelude.mlt") - }; - let mut file = OpenOptions::new().read(true).write(true).create(true).open(path).await?; - let mut s = String::new(); - let _ = file.read_to_string(&mut s).await?; - let rules = match parse_rules(&s) { - | Ok(rules) => rules, - | Err(error) => { - eprintln!("{}", error); - return Ok(()) - }, - }; - - let module = prolog::Module::new("mlatu"); - - let clauses = match codegen::generate(&ctx, &rules) { - | Ok(clauses) => clauses, - | Err(error) => { - eprintln!("Error while compiling rules: {}", error); - return Ok(()) - }, - }; - - if let Err(error) = - clauses.into_iter() - .try_for_each(|clause| ctx.assert(&clause.clause, Some(&module), AssertLocation::Last)) - { - eprintln!("Error while compiling rules: {}", error); - return Ok(()) - }; - - let stdin = io::stdin(); - let mut stdout = io::stdout(); - - loop { - let mut contents = String::new(); - print!(">>> "); - stdout.flush().await?; - stdin.read_line(&mut contents).await?; - - if contents.trim() == "exit" || contents.trim() == "quit" || contents.is_empty() { - println!("Goodbye!"); - break Ok(()) +async fn load_files(files:Vec) -> Result, String> { + let mut rules = Vec::new(); + for file in files { + match read_to_string(file).await { + | Ok(string) => match parse_rules(&string) { + | Ok(rs) => rules.extend(rs), + | Err(error) => return Err(error), + }, + | Err(error) => return Err(error.to_string()), } + } + Ok(rules) +} - let terms = match parse_terms(&contents) { - | Ok(terms) => terms, - | Err(error) => { - eprintln!("{}", error); - continue +#[tokio::main] +async fn main() -> std::io::Result<()> { + let matches = App::new("mlatu") + .version("0.1") + .author("Caden Haustein ") + .about("the mlatu language interface") + .arg(Arg::with_name("FILES").multiple(true).help("Sets the rule files to use")). + arg(Arg::with_name("no-prelude").long("no-prelude").help("Doesn't load the normal prelude")) + .subcommand(SubCommand::with_name("edit") + .about("the mlatu structured editor") + .version("0.1") + .author("Caden Haustein ") + .arg(Arg::with_name("FILE").required(true).help("Sets the rule file to edit") + )) + .get_matches(); + let mut files = Vec::new(); + if !matches.is_present("no-prelude") { + files.push("./prelude.mlt".to_string()); + } + if let Some(args) = matches.values_of("FILES") { + files.extend(args.map(ToOwned::to_owned)); + } + match load_files(files).await { + | Ok(rules) => match matches.subcommand() { + | ("edit", Some(sub_matches)) => { + let file = File::create(sub_matches.value_of("FILE").unwrap()).await?; + Editor::new(file, rules)?.run().await; }, - }; + | _ => { + let (prolog_tx, mut interactive_rx) = unbounded_channel(); + let (interactive_tx, prolog_rx) = unbounded_channel(); - let (list, other) = match codegen::generate_query(&ctx, &terms) { - | Ok(q) => q, - | Err(error) => { - eprintln!("Error while compiling query: {}", error); - continue - }, - }; + std::thread::spawn(move || { + prolog::thread(|ctx, module| { + let clauses = codegen::generate(ctx, &rules).unwrap(); + clauses.into_iter() + .try_for_each(|clause| { + ctx.assert(&clause.clause, Some(module), AssertLocation::Last) + }) + .unwrap(); + }, + &prolog_tx, + prolog_rx) + }); - if let Err(error) = ctx.call_once(prolog::pred!(mlatu: rewrite / 2), [&list, &other]) { - eprintln!("Error while executing query: {}", error); - continue - } - // TODO: proper printing - let canon = match ctx.canonical(&other) { - | Some(s) => s, - | None => { - eprintln!("Could not format output"); - continue + Interactive::new(interactive_tx)?.run(&mut interactive_rx).await; }, - }; - - println!("==> {}", canon); + }, + | Err(error) => eprintln!("Error while reading rule files: {}", error), } + + Ok(()) } diff --git a/src/prolog.rs b/src/prolog.rs index 117f39d..e9ba138 100644 --- a/src/prolog.rs +++ b/src/prolog.rs @@ -1,14 +1,27 @@ pub use swipl::fli; pub use swipl::prelude::*; +use crate::ast::Term as AstTerm; + pub mod codegen; pub mod util; +use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; pub use util::ContextExt; static SAVED_STATE:&[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/mlatu.pl.save")); +type PrologContext<'a> = Context<'a, ActivatedEngine<'a>>; + +/// Initialize the prolog engine. +/// +/// # Panics +/// +/// This function will panic if the engine has already been initialized. #[must_use] pub fn init_engine() -> Engine { + // To maintain safety, SWIPL must not have been initialized yet. + assert!(!is_swipl_initialized(), + "SWIPL must not be initialized when getting an engine with a saved state"); let bytes = SAVED_STATE.as_ptr(); // SAFETY: This is called *before* `PL_initialise` (called when creating an // engine). This data is also valid and the length is correct, as we take the @@ -20,3 +33,39 @@ pub fn init_engine() -> Engine { Engine::new() } + +pub fn thread(start:impl for<'a> FnOnce(&'a PrologContext<'a>, &'a Module), + tx:&UnboundedSender, String>>, + mut rx:UnboundedReceiver>) { + let engine = init_engine(); + let ctx:PrologContext<'_> = engine.activate().into(); + + let module = Module::new("mlatu"); + + start(&ctx, &module); + + while let Some(terms) = rx.blocking_recv() { + if terms.is_empty() { + tx.send(Ok(vec![])).expect("send result"); + } + + let (list, other) = match codegen::generate_query(&ctx, &*terms) { + | Ok(x) => x, + | Err(_) => { + tx.send(Err("Error while compiling query.".to_string())).expect("send result"); + continue + }, + }; + + if ctx.call_once(pred![mlatu: rewrite / 2], [&list, &other]).is_err() { + tx.send(Err("Error while executing query.".to_string())).expect("send result"); + continue + } + + tx.send(match other.get::>() { + | Ok(terms) => Ok(terms.into_iter().rev().collect()), + | Err(_) => Err("Error while getting result.".to_string()), + }) + .expect("send result"); + } +} diff --git a/src/terminal.rs b/src/terminal.rs deleted file mode 100644 index 6d5ec4e..0000000 --- a/src/terminal.rs +++ /dev/null @@ -1,46 +0,0 @@ -use async_std::io; -use async_std::io::{stdout, WriteExt}; -use termion::event::Key; -use termion::input::TermRead; -use termion::raw::{IntoRawMode, RawTerminal}; - -pub struct Terminal { - width:u16, - height:u16, - _stdout:RawTerminal, -} - -impl Terminal { - pub fn new() -> io::Result { - let size = termion::terminal_size()?; - Ok(Self { width:size.0, height:size.1, _stdout:std::io::stdout().into_raw_mode()? }) - } - - pub const fn width(&self) -> u16 { self.width } - - pub const fn height(&self) -> u16 { self.height } - - pub fn clear_screen() { print!("{}", termion::clear::All) } - - pub fn cursor_position(x:u16, y:u16) { - let x = x.saturating_add(1); - let y = y.saturating_add(1); - print!("{}", termion::cursor::Goto(x, y)); - } - - pub async fn flush() -> io::Result<()> { stdout().flush().await } - - pub fn read_key() -> io::Result { - loop { - if let Some(key) = std::io::stdin().lock().keys().next() { - return key - } - } - } - - pub fn cursor_hide() { print!("{}", termion::cursor::Hide) } - - pub fn cursor_show() { print!("{}", termion::cursor::Show) } - - pub fn clear_current_line() { print!("{}", termion::clear::CurrentLine) } -} diff --git a/src/view.rs b/src/view.rs new file mode 100644 index 0000000..a4bc018 --- /dev/null +++ b/src/view.rs @@ -0,0 +1,173 @@ +use std::io; +use std::io::{stdout, Write}; +use std::sync::Arc; + +use crossterm::event::{Event, EventStream, KeyEvent}; +use crossterm::terminal::ClearType; +use crossterm::{cursor, queue, terminal, Command}; +use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use tokio_stream::StreamExt; + +use crate::ast::Rule; + +#[derive(Clone)] +pub enum State { + InLeft(usize), + InRight(usize), + AtLeft, + AtRight, + Editing(String, Box), +} + +pub struct View { + width:u16, + height:u16, + sides:Arc>, + labels:(String, String), + default_status:String, +} + +impl View { + pub fn new(sides:Arc>, labels:(String, String), default_status:String) + -> io::Result { + let (width, height) = terminal::size()?; + Ok(Self { width, height, sides, labels, default_status }) + } + + fn queue(command:impl Command) -> io::Result<()> { queue!(stdout(), command) } + + pub fn clear_screen() -> io::Result<()> { Self::queue(terminal::Clear(ClearType::All)) } + + pub fn flush() -> io::Result<()> { stdout().flush() } + + pub async fn read_key(&mut self) -> io::Result { + let mut stream = EventStream::new(); + loop { + match stream.next().await { + | Some(Ok(Event::Key(key))) => return Ok(key), + | Some(Ok(Event::Resize(columns, rows))) => { + self.width = columns; + self.height = rows; + }, + | Some(Ok(Event::Mouse(_))) => {}, + | Some(Err(e)) => return Err(e), + | None => + return Err(io::Error::new(io::ErrorKind::Other, "unexpected end of terminal input")), + } + } + } + + const fn left_half_width(&self, sep_len:u16) -> u16 { + // Width of left box + // half of the width, minus first separator + // = l/2 - s + // where l is the width and s is the separator length + self.width / 2 - sep_len + } + + const fn right_half_width(&self, sep_len:u16) -> u16 { + // Width of right box + // entire width, minus first box, minux all 3 separators + // = l - (l/2 - s) - 3s + // = l - l/s + s - 3s + // = l - l/2 - 2s + // where l is the width and s is the separator length + self.width - self.width / 2 - 2 * sep_len + } + + async fn make_left_half(&self, row:u16, s:&mut String) { + let guard = self.sides.read().await; + let term = guard.0.get(usize::from(row) - 2).map_or_else(String::new, ToString::to_string); + let width = self.left_half_width(1); + s.push_str(&format!("{0: ^1$}", term, width.into())); + } + + async fn make_right_half(&self, row:u16, s:&mut String) { + let width = self.right_half_width(1); + let guard = self.sides.read().await; + let term = guard.1.get(usize::from(row) - 2).map_or_else(String::new, ToString::to_string); + s.push_str(&format!("{0: ^1$}", term, width.into())); + } + + async fn get_target(&self, state:&State) -> (u16, u16) { + let guard = self.sides.read().await; + match state { + | State::InLeft(index) => { + let term = guard.0.get(*index).expect("bounds check failed"); + let p_t = term.to_string(); + (self.width / 4 + - u16::try_from(p_t.len()).expect("pattern term text is greater than 2^16 characters") / 2, + u16::try_from(*index).expect("pattern terms longer than 2^16 terms") + 2) + }, + | State::AtLeft => (self.width / 4 - 1, 2), + | State::InRight(index) => { + let term = guard.1.get(*index).expect("bounds check failed"); + let r_t = term.to_string(); + (3 * self.width / 4 + - (u16::try_from(r_t.len()).expect("replacement term text is greater than 2^16 \ + characters")) + / 2, + u16::try_from(*index).expect("replacement terms longer than 2^16 terms") + 2) + }, + | State::AtRight => (3 * self.width / 4 - 1, 2), + | State::Editing(ref s, _) => + (self.width / 2 + + (u16::try_from(s.len()).expect("input field text is greater than 2^16 characters") + 1) + / 2, + 0), + } + } + + fn display_status(&mut self, state:&State) { + let status = match &state { + | State::Editing(msg, _) => msg.clone(), + | _ => self.default_status.clone(), + }; + let width = usize::from(self.width); + print!("{0: ^1$}\r\n", status, width); + print!("-{0:-^1$}-{2:-^3$}-\r\n", + self.labels.0, + self.left_half_width(1).into(), + self.labels.1, + self.right_half_width(1).into()); + } + + async fn display(&mut self, state:State) -> io::Result<()> { + self.display_status(&state); + for row in 2..self.height - 1 { + let mut s = "|".to_owned(); + Self::queue(terminal::Clear(ClearType::CurrentLine))?; + self.make_left_half(row, &mut s).await; + s.push('|'); + self.make_right_half(row, &mut s).await; + s.push('|'); + println!("{}\r", s); + } + let mut s = "|".to_owned(); + self.make_left_half(self.height, &mut s).await; + s.push('|'); + self.make_right_half(self.height, &mut s).await; + s.push('|'); + print!("{}", s); + Ok(()) + } + + pub async fn refresh_screen(&mut self, state:&State, should_quit:bool) -> io::Result<()> { + Self::queue(cursor::Hide)?; + Self::queue(cursor::MoveTo(0, 0))?; + if should_quit { + Self::clear_screen()?; + println!("Goodbye.\r"); + } else { + self.display(state.clone()).await?; + let (x, y) = self.get_target(state).await; + Self::queue(cursor::MoveTo(x, y))?; + } + Self::queue(cursor::Show)?; + Self::flush() + } + + pub async fn read(&'_ self) -> RwLockReadGuard<'_, Rule> { self.sides.read().await } + + pub async fn write(&'_ self) -> RwLockWriteGuard<'_, Rule> { self.sides.write().await } +}