├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── examples └── example.gif └── src ├── coalesce.rs ├── coalescence.rs ├── context.rs ├── error.rs ├── interface ├── basic.rs ├── check.rs ├── history.rs ├── interface.rs ├── mod.rs └── render.rs ├── item.rs ├── lexer.rs ├── main.rs ├── node.rs ├── parse.rs ├── span.rs ├── tests ├── evaluation.rs └── mod.rs └── token.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .idea 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "bitflags" 5 | version = "1.2.1" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "calculator" 10 | version = "0.1.0" 11 | dependencies = [ 12 | "crossterm 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", 13 | ] 14 | 15 | [[package]] 16 | name = "cfg-if" 17 | version = "0.1.10" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | 20 | [[package]] 21 | name = "crossterm" 22 | version = "0.12.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | dependencies = [ 25 | "crossterm_cursor 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 26 | "crossterm_input 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 27 | "crossterm_screen 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 28 | "crossterm_style 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 29 | "crossterm_terminal 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 30 | "crossterm_utils 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 31 | ] 32 | 33 | [[package]] 34 | name = "crossterm_cursor" 35 | version = "0.4.0" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | dependencies = [ 38 | "crossterm_input 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 39 | "crossterm_utils 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 40 | "crossterm_winapi 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 41 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 42 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 43 | ] 44 | 45 | [[package]] 46 | name = "crossterm_input" 47 | version = "0.5.0" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | dependencies = [ 50 | "crossterm_screen 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 51 | "crossterm_utils 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 52 | "crossterm_winapi 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 53 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 54 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 55 | "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", 56 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 57 | ] 58 | 59 | [[package]] 60 | name = "crossterm_screen" 61 | version = "0.3.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | dependencies = [ 64 | "crossterm_utils 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 65 | "crossterm_winapi 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 66 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 67 | ] 68 | 69 | [[package]] 70 | name = "crossterm_style" 71 | version = "0.5.2" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | dependencies = [ 74 | "crossterm_utils 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 75 | "crossterm_winapi 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 76 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 77 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 78 | ] 79 | 80 | [[package]] 81 | name = "crossterm_terminal" 82 | version = "0.3.2" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | dependencies = [ 85 | "crossterm_cursor 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 86 | "crossterm_utils 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 87 | "crossterm_winapi 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 88 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 89 | ] 90 | 91 | [[package]] 92 | name = "crossterm_utils" 93 | version = "0.4.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | dependencies = [ 96 | "crossterm_winapi 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 97 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 98 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 99 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 100 | ] 101 | 102 | [[package]] 103 | name = "crossterm_winapi" 104 | version = "0.3.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | dependencies = [ 107 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 108 | ] 109 | 110 | [[package]] 111 | name = "fuchsia-zircon" 112 | version = "0.3.3" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | dependencies = [ 115 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 117 | ] 118 | 119 | [[package]] 120 | name = "fuchsia-zircon-sys" 121 | version = "0.3.3" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | 124 | [[package]] 125 | name = "iovec" 126 | version = "0.1.4" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | dependencies = [ 129 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 130 | ] 131 | 132 | [[package]] 133 | name = "kernel32-sys" 134 | version = "0.2.2" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | dependencies = [ 137 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 138 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 139 | ] 140 | 141 | [[package]] 142 | name = "lazy_static" 143 | version = "1.4.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | 146 | [[package]] 147 | name = "libc" 148 | version = "0.2.65" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | 151 | [[package]] 152 | name = "log" 153 | version = "0.4.8" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | dependencies = [ 156 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 157 | ] 158 | 159 | [[package]] 160 | name = "mio" 161 | version = "0.6.19" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | dependencies = [ 164 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 165 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 166 | "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 167 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 168 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 169 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 170 | "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 171 | "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 172 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 173 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 174 | ] 175 | 176 | [[package]] 177 | name = "miow" 178 | version = "0.2.1" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | dependencies = [ 181 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 182 | "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 183 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 184 | "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 185 | ] 186 | 187 | [[package]] 188 | name = "net2" 189 | version = "0.2.33" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | dependencies = [ 192 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 193 | "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", 194 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 195 | ] 196 | 197 | [[package]] 198 | name = "slab" 199 | version = "0.4.2" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | 202 | [[package]] 203 | name = "winapi" 204 | version = "0.2.8" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | 207 | [[package]] 208 | name = "winapi" 209 | version = "0.3.8" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | dependencies = [ 212 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 213 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 214 | ] 215 | 216 | [[package]] 217 | name = "winapi-build" 218 | version = "0.1.1" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | 221 | [[package]] 222 | name = "winapi-i686-pc-windows-gnu" 223 | version = "0.4.0" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | 226 | [[package]] 227 | name = "winapi-x86_64-pc-windows-gnu" 228 | version = "0.4.0" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | 231 | [[package]] 232 | name = "ws2_32-sys" 233 | version = "0.2.1" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | dependencies = [ 236 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 237 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 238 | ] 239 | 240 | [metadata] 241 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 242 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 243 | "checksum crossterm 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e01ed8bce37de312d284416e16a48c427dde307f4656319cd67f63b3b37e53f9" 244 | "checksum crossterm_cursor 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c48e70d4c84ca3080e43f95bcc69fbea4750dd8ae7e864f54ab2847c9fa842fb" 245 | "checksum crossterm_input 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39fafa96a623903f0d9232a13ee43f69334caa770fb412f4319772a9db25952c" 246 | "checksum crossterm_screen 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a08dfba7fade62a6c2c48ad6451e288f15fd9ffa640078fc4b08948e81d19db7" 247 | "checksum crossterm_style 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b456402b03be74b9f765c85edf36e34f8292d3839312b458b015e52f1fc81d86" 248 | "checksum crossterm_terminal 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b3f09b946c37159123fa3fd70215e4ac99dbd660a80b3878f4612f1b9ed4bc88" 249 | "checksum crossterm_utils 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c13047e5f7265fe853d54211dbb9bce57220f62d952d48556299f7a65d564a3" 250 | "checksum crossterm_winapi 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df25e92a352488d9b3e0215e4e99402945993026159f98b477047719f16a6530" 251 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 252 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 253 | "checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 254 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 255 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 256 | "checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" 257 | "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 258 | "checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" 259 | "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 260 | "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" 261 | "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 262 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 263 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 264 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 265 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 266 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 267 | "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 268 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calculator" 3 | version = "0.1.0" 4 | authors = ["Techno-coder <8334328+Techno-coder@users.noreply.github.com>"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | crossterm = "^0.12" 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Technocoder (Techno-coder) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # calculator 2 | 3 | ![](examples/example.gif) 4 | 5 | ``` 6 | >> (10) - 2 + 4; 7 | [0] 4 8 | >> 2 * 0x03 9 | [1] 6 10 | >> $ ^ $$ 11 | [2] 1296 12 | >> $2 % 5 13 | [3] 1 14 | ``` 15 | 16 | ## Installation 17 | ``` 18 | $ cargo install --locked --git https://github.com/Techno-coder/calculator 19 | ``` 20 | 21 | ## Execution 22 | ``` 23 | $ calculator 24 | ``` 25 | Basic mode does not update on each key press and may work better for less 26 | advanced terminals. 27 | ``` 28 | $ calculator -b/--basic 29 | ``` 30 | Evaluation mode reads from the standard input pipe and outputs results directly. 31 | ``` 32 | $ echo expression | calculator -e/--evaluation 33 | ``` 34 | 35 | ## Arithmetic Operators 36 | In order of precedence: 37 | * `+` - Add 38 | * `-` - Minus 39 | * `*` - Multiply 40 | * `/` - Divide 41 | * `%` - Modulo 42 | * `^` - Power 43 | 44 | ## Numerical Formats 45 | * `0x000a` - Hexadecimal 46 | * `0b1010` - Binary 47 | * `0o0012` - Octal 48 | * `1.0` - Floating 49 | * `1e1` - Scientific 50 | 51 | ## Variables 52 | * `$` - Last evaluation result 53 | * `$$` - Second last evaluation result 54 | * `$$...$` - Arbitrary position evaluation result 55 | * `$0` - Variable with identifier `0` 56 | * `$aa` - Variable with identifier `aa` 57 | 58 | ## Functions 59 | ``` 60 | >> function argument 61 | ``` 62 | Functions take the term immediately to the right. 63 | Whitespace is required after the function name. 64 | 65 | * `abs` - Absolute value 66 | * `sqrt` - Square root 67 | * `cbrt` - Cube root 68 | * `ln` - Natural logarithm 69 | * `log2` - Binary logarithm 70 | * `log10` - Decimal logarithm 71 | 72 | ### Trigonometry 73 | * `sin` - Sine 74 | * `cos` - Cosine 75 | * `tan` - Tangent 76 | * `asin` - Inverse sine 77 | * `acos` - Inverse cosine 78 | * `atan` - Inverse tangent 79 | 80 | The trigonometric functions can take and return degrees by appending `'`: 81 | ``` 82 | >> asin' 1 83 | [0] 90 84 | ``` 85 | 86 | ## Constants 87 | * `e` - Euler number 88 | * `pi` - Pi (3.14) 89 | 90 | ## Coalescence 91 | * `;` - Coalesce operator 92 | 93 | The coalesce operator combines the previous two terms into a single node. 94 | This eliminates the need for parentheses. 95 | 96 | ``` 97 | >> 10 - 2 + 4; 98 | ``` 99 | is equivalent to: 100 | ``` 101 | >> 10 - (2 + 4) 102 | ``` 103 | 104 | Repetitions of the coalesce operator will combine more than two terms: 105 | ``` 106 | >> 1 / 1 + 2 + 3;; 107 | ``` 108 | is equivalent to: 109 | ``` 110 | >> 1 / (1 + 2 + 3) 111 | ``` 112 | Sometimes a combined term is nested inside another: 113 | ``` 114 | >> 1 / (2 - (3 + 4)) 115 | ``` 116 | This can be achieved by leaving whitespace after the first coalescence: 117 | ``` 118 | >> 1 / 2 - 3 + 4; ; 119 | ``` 120 | -------------------------------------------------------------------------------- /examples/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Techno-coder/calculator/e65b59fdec94b8c9898a19439dce9d7712280f16/examples/example.gif -------------------------------------------------------------------------------- /src/coalesce.rs: -------------------------------------------------------------------------------- 1 | use crate::coalescence::Coalescence; 2 | use crate::error::Error; 3 | use crate::item::Function; 4 | use crate::lexer::Lexer; 5 | use crate::span::{Span, Spanned}; 6 | use crate::token::{Operator, Token}; 7 | 8 | pub fn coalesce_root(lexer: &mut Lexer) -> Result> { 9 | coalesce(lexer, false, false) 10 | } 11 | 12 | fn coalesce(lexer: &mut Lexer, mut last_valued: bool, expect_parenthesis: bool) 13 | -> Result> { 14 | let mut last_byte_end = 0; 15 | let mut coalesces = Vec::new(); 16 | while let Some(token) = lexer.next() { 17 | let token = token?; 18 | let span = token.span; 19 | last_byte_end = token.span.byte_end(); 20 | match token.node { 21 | Token::ParenthesisClose if expect_parenthesis => match coalesces.is_empty() { 22 | true => return Err(token.map(Error::EmptyBrackets)), 23 | false => break, 24 | }, 25 | Token::ParenthesisClose => return Err(token.map(Error::MismatchedBracket)), 26 | Token::ParenthesisOpen => { 27 | if last_valued { 28 | let operator = Spanned::new(Operator::Multiply, span); 29 | coalesces.push(Coalescence::Operator(operator)); 30 | } 31 | 32 | coalesces.push(coalesce(lexer, false, true)?); 33 | last_valued = true; 34 | } 35 | Token::Operator(operator) => { 36 | if !last_valued { 37 | match operator { 38 | Operator::Minus => { 39 | let function = Spanned::new(Function::UnaryMinus, span); 40 | coalesces.push(Coalescence::Function(function)); 41 | continue; 42 | } 43 | _ => return Err(token.map(Error::ExpectedValued)), 44 | } 45 | } 46 | 47 | coalesces.push(Coalescence::Operator(token.map(operator))); 48 | last_valued = false; 49 | } 50 | Token::Terminal(terminal) => value(&mut coalesces, &mut last_valued, 51 | Coalescence::Terminal(token.map(terminal)), span)?, 52 | Token::Variable(variable) => value(&mut coalesces, &mut last_valued, 53 | Coalescence::Variable(Spanned::new(variable, span)), span)?, 54 | Token::Function(function) => match last_valued { 55 | false => coalesces.push(Coalescence::Function(Spanned::new(function, span))), 56 | true => return Err(Spanned::new(Error::ExpectedOperator, span)), 57 | }, 58 | Token::Constant(constant) => value(&mut coalesces, &mut last_valued, 59 | Coalescence::Terminal(Spanned::new(constant.value(), span)), span)?, 60 | Token::Coalesce(mut count) => { 61 | count += 1; 62 | let mut iterator = coalesces.iter().enumerate().rev(); 63 | while let Some((index, coalesce)) = iterator.next() { 64 | match coalesce { 65 | Coalescence::Operator(_) => continue, 66 | _ => count -= 1, 67 | } 68 | 69 | if count == 0 { 70 | let coalescence = coalesces.split_off(index); 71 | coalesces.push(Coalescence::Multiple(coalescence)); 72 | break; 73 | } 74 | } 75 | 76 | if count > 0 { 77 | return Err(token.map(Error::InvalidCoalesce)); 78 | } 79 | } 80 | } 81 | } 82 | 83 | let last_span = Span(last_byte_end, last_byte_end + 1); 84 | match last_valued { 85 | true => Ok(Coalescence::Multiple(coalesces)), 86 | false => Err(Spanned::new(Error::ExpectedValued, last_span)) 87 | } 88 | } 89 | 90 | fn value(coalesces: &mut Vec, last_valued: &mut bool, 91 | value: Coalescence, span: Span) -> Result<(), Spanned> { 92 | if *last_valued { 93 | match coalesces.last() { 94 | Some(Coalescence::Multiple(_)) => { 95 | let byte_start = value.byte_start(); 96 | let span = Span(byte_start, byte_start + 1); 97 | let operator = Spanned::new(Operator::Multiply, span); 98 | coalesces.push(Coalescence::Operator(operator)); 99 | } 100 | _ => return Err(Spanned::new(Error::ExpectedOperator, span)), 101 | } 102 | } 103 | 104 | *last_valued = true; 105 | Ok(coalesces.push(value)) 106 | } 107 | -------------------------------------------------------------------------------- /src/coalescence.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::error::Error; 3 | use crate::item::Function; 4 | use crate::span::Spanned; 5 | use crate::token::Operator; 6 | 7 | #[derive(Debug)] 8 | pub enum Coalescence { 9 | Multiple(Vec), 10 | Operator(Spanned), 11 | Terminal(Spanned), 12 | Variable(Spanned), 13 | Function(Spanned), 14 | } 15 | 16 | impl Coalescence { 17 | pub fn verify(&self, context: &Context) -> Result<(), Spanned> { 18 | match self { 19 | Coalescence::Multiple(coalesces) => coalesces.iter() 20 | .try_for_each(|coalesce| coalesce.verify(context)), 21 | Coalescence::Variable(variable) => context.variable(&variable.node) 22 | .map_err(|error| Spanned::new(error, variable.span)).map(|_| ()), 23 | _ => Ok(()), 24 | } 25 | } 26 | 27 | pub fn coalesce_anchors(&self) -> Vec { 28 | match self { 29 | Coalescence::Multiple(coalesces) => { 30 | let mut anchors = Vec::new(); 31 | let mut coalesces = coalesces.iter(); 32 | while let Some(coalesce) = coalesces.next() { 33 | match coalesce { 34 | Coalescence::Operator(_) => continue, 35 | _ => anchors.push(coalesce.byte_start()), 36 | } 37 | } 38 | anchors 39 | } 40 | Coalescence::Terminal(_) => vec![self.byte_start()], 41 | Coalescence::Variable(_) => vec![self.byte_start()], 42 | Coalescence::Function(_) => vec![self.byte_start()], 43 | Coalescence::Operator(_) => vec![], 44 | } 45 | } 46 | 47 | pub fn byte_start(&self) -> usize { 48 | match self { 49 | Coalescence::Multiple(coalesces) => coalesces.first().unwrap().byte_start(), 50 | Coalescence::Operator(operator) => operator.span.byte_start(), 51 | Coalescence::Terminal(terminal) => terminal.span.byte_start(), 52 | Coalescence::Variable(variable) => variable.span.byte_start(), 53 | Coalescence::Function(function) => function.span.byte_start(), 54 | } 55 | } 56 | 57 | pub fn byte_end(&self) -> usize { 58 | match self { 59 | Coalescence::Multiple(coalesces) => coalesces.last().unwrap().byte_end(), 60 | Coalescence::Operator(operator) => operator.span.byte_end(), 61 | Coalescence::Terminal(terminal) => terminal.span.byte_end(), 62 | Coalescence::Variable(variable) => variable.span.byte_end(), 63 | Coalescence::Function(function) => function.span.byte_end(), 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::error::Error; 4 | 5 | #[derive(Debug, Default)] 6 | pub struct Context { 7 | current_index: usize, 8 | variables: HashMap, 9 | history: Vec, 10 | 11 | pub cursor_position: usize, 12 | pub history_offset: usize, 13 | pub expression: String, 14 | } 15 | 16 | impl Context { 17 | pub fn push_value(&mut self, value: f64) -> usize { 18 | loop { 19 | let index_key = format!("{:x}", self.current_index); 20 | if self.variables.contains_key(&index_key) { 21 | self.current_index += 1; 22 | continue; 23 | } 24 | 25 | self.current_index += 1; 26 | self.variables.insert(index_key, value); 27 | break self.current_index - 1; 28 | } 29 | } 30 | 31 | pub fn variable(&self, variable: &str) -> Result { 32 | Ok(match variable.chars().all(|character| character == '$') { 33 | false => self.variables.get(variable) 34 | .ok_or_else(|| Error::UndefinedVariable(variable.to_owned())), 35 | true => { 36 | let index = self.current_index.checked_sub(variable.len() + 1) 37 | .ok_or(Error::InvalidEvaluationOffset)?; 38 | self.variables.get(&format!("{:x}", index)) 39 | .ok_or(Error::InvalidEvaluationOffset) 40 | } 41 | }?.clone()) 42 | } 43 | 44 | pub fn push_history(&mut self, expression: String) { 45 | self.history.push(expression); 46 | self.history_offset = 0; 47 | } 48 | 49 | pub fn history(&self) -> Option<&str> { 50 | let index = self.history.len().checked_sub(self.history_offset)?; 51 | self.history.get(index).map(String::as_str) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(Debug, PartialEq)] 4 | pub enum Error { 5 | UndefinedVariable(String), 6 | InvalidCharacter(char), 7 | InvalidEvaluationOffset, 8 | InvalidTerminal, 9 | InvalidItem, 10 | ExpectedValued, 11 | ExpectedOperator, 12 | MismatchedBracket, 13 | EmptyBrackets, 14 | InvalidCoalesce, 15 | ZeroDivision, 16 | NegativeRoot, 17 | } 18 | 19 | impl fmt::Display for Error { 20 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 21 | match self { 22 | Error::UndefinedVariable(variable) => 23 | write!(f, "Undefined variable: {}", variable), 24 | Error::InvalidCharacter(character) => 25 | write!(f, "Invalid input character: '{}'", character), 26 | Error::InvalidEvaluationOffset => 27 | write!(f, "Invalid evaluation offset"), 28 | Error::InvalidTerminal => 29 | write!(f, "Invalid number"), 30 | Error::InvalidItem => 31 | write!(f, "Invalid function or constant"), 32 | Error::ExpectedValued => 33 | write!(f, "Expected a number, variable or constant"), 34 | Error::ExpectedOperator => 35 | write!(f, "Expected an operator"), 36 | Error::MismatchedBracket => 37 | write!(f, "Bracket has no matching pair"), 38 | Error::EmptyBrackets => 39 | write!(f, "Bracket pair is empty"), 40 | Error::InvalidCoalesce => 41 | write!(f, "Invalid coalesce"), 42 | Error::ZeroDivision => 43 | write!(f, "Division by zero"), 44 | Error::NegativeRoot => 45 | write!(f, "Negative root is undefined"), 46 | } 47 | } 48 | } 49 | 50 | impl std::error::Error for Error {} 51 | -------------------------------------------------------------------------------- /src/interface/basic.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, stdin, stdout, Write}; 2 | 3 | use crossterm::{Colorize, Styler}; 4 | 5 | use crate::context::Context; 6 | use crate::error::Error; 7 | use crate::span::{Span, Spanned}; 8 | 9 | pub fn basic() -> super::Result { 10 | print!("{}", super::PROMPT.white().bold()); 11 | stdout().flush()?; 12 | 13 | let context = &mut Context::default(); 14 | while let Ok(count) = stdin().read_line(&mut context.expression) { 15 | if count == 0 { 16 | break; 17 | } 18 | 19 | match evaluate(context) { 20 | Ok(evaluation) => { 21 | let index = context.push_value(evaluation); 22 | super::render::value_index(index); 23 | super::render::evaluation(evaluation, None); 24 | println!(); 25 | } 26 | Err(error) => { 27 | let Span(byte_start, byte_end) = error.span; 28 | let specific = "^".repeat(byte_end - byte_start).to_owned(); 29 | eprintln!("{}{} {}", " ".repeat(super::PROMPT.len() + byte_start), 30 | specific, error.node); 31 | } 32 | } 33 | 34 | print!("{}", super::PROMPT.white().bold()); 35 | context.expression.clear(); 36 | stdout().flush()?; 37 | } 38 | 39 | Ok(()) 40 | } 41 | 42 | pub fn evaluate_direct() -> super::Result { 43 | let context = &mut Context::default(); 44 | stdin().read_to_string(&mut context.expression)?; 45 | println!("{}", evaluate(context).map_err(|error| error.node)?); 46 | Ok(()) 47 | } 48 | 49 | pub fn evaluate(context: &mut Context) -> Result> { 50 | let lexer = &mut crate::lexer::Lexer::new(&context.expression); 51 | let coalescence = crate::coalesce::coalesce_root(lexer)?; 52 | let node = crate::parse::parse_root(coalescence); 53 | node.evaluate(context) 54 | } 55 | -------------------------------------------------------------------------------- /src/interface/check.rs: -------------------------------------------------------------------------------- 1 | use std::io::{stdout, Write}; 2 | 3 | use crossterm::*; 4 | 5 | use crate::coalescence::Coalescence; 6 | use crate::context::Context; 7 | 8 | use super::render; 9 | 10 | type CheckResult = std::result::Result, Box>; 11 | 12 | pub fn check(context: &Context) -> CheckResult { 13 | let lexer = &mut crate::lexer::Lexer::new(&context.expression); 14 | let coalescence = crate::coalesce::coalesce_root(lexer); 15 | if let Err(error) = coalescence { 16 | render::line_error(&error)?; 17 | return Ok(None); 18 | } 19 | 20 | let coalescence = coalescence.unwrap(); 21 | coalesce_anchors(&coalescence)?; 22 | 23 | if let Err(error) = coalescence.verify(context) { 24 | render::line_error(&error)?; 25 | return Ok(None); 26 | } 27 | 28 | Ok(Some(coalescence)) 29 | } 30 | 31 | pub fn coalesce_anchors(coalescence: &Coalescence) -> super::Result { 32 | render::clear_buffer()?; 33 | let anchors = coalescence.coalesce_anchors(); 34 | queue!(stdout(), SavePos, Down(1), SetFg(Color::Yellow))?; 35 | 36 | let (_, row) = crossterm::cursor().pos()?; 37 | anchors.iter().try_for_each(|offset| queue!(stdout(), 38 | Goto((super::PROMPT.len() + offset) as u16, row), 39 | Output("^".to_string())))?; 40 | 41 | if let Some(offset) = anchors.last() { 42 | let byte_end = coalescence.byte_end(); 43 | render::anchor_start(match byte_end == *offset + 1 { 44 | false => *offset + 1, 45 | true => *offset, 46 | })?; 47 | 48 | let coalesce_length = (coalescence.byte_end() - *offset).saturating_sub(2); 49 | queue!(stdout(), Output("-".repeat(coalesce_length)), Output("^".to_string()))?; 50 | } 51 | 52 | Ok(queue!(stdout(), SetFg(Color::Reset), ResetPos)?) 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/interface/history.rs: -------------------------------------------------------------------------------- 1 | use std::io::{stdout, Write}; 2 | 3 | use crossterm::*; 4 | 5 | use crate::context::Context; 6 | 7 | use super::{interface, Result}; 8 | 9 | pub fn history_up(context: &mut Context) -> Result { 10 | context.history_offset += 1; 11 | match context.history().map(ToOwned::to_owned) { 12 | None => context.history_offset -= 1, 13 | Some(history) => { 14 | if context.expression.len() > 0 { 15 | queue!(stdout(), Left(context.expression.len() as u16))?; 16 | } 17 | 18 | context.expression = history; 19 | context.cursor_position = context.expression.len(); 20 | queue!(stdout(), Clear(ClearType::UntilNewLine), Output(context.expression.clone()))?; 21 | interface::evaluate(context, false)?; 22 | } 23 | } 24 | Ok(()) 25 | } 26 | 27 | pub fn history_down(context: &mut Context) -> Result { 28 | context.history_offset = context.history_offset.saturating_sub(1); 29 | let current_length = context.expression.len(); 30 | match context.history_offset { 31 | 0 => context.expression.clear(), 32 | _ => context.expression = context.history().unwrap().to_owned(), 33 | } 34 | 35 | let expression = &mut context.expression; 36 | context.cursor_position = expression.len(); 37 | if current_length > 0 { 38 | queue!(stdout(), Left(current_length as u16))?; 39 | } 40 | 41 | queue!(stdout(), Clear(ClearType::UntilNewLine), Output(expression.clone()))?; 42 | interface::evaluate(context, false)?; 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /src/interface/interface.rs: -------------------------------------------------------------------------------- 1 | use std::io::{stdout, Write}; 2 | 3 | use crossterm::*; 4 | 5 | use crate::context::Context; 6 | 7 | use super::{render, Result}; 8 | 9 | /// Spawns an interface with immediate expression verification. 10 | pub fn interface() -> Result { 11 | let mut reader = crossterm::input().read_sync(); 12 | let _screen = RawScreen::into_raw_mode()?; 13 | print!("{}", super::PROMPT.white().bold()); 14 | stdout().flush()?; 15 | 16 | let context = &mut Context::default(); 17 | while let Some(event) = reader.next() { 18 | match event { 19 | InputEvent::Keyboard(event) => match event { 20 | KeyEvent::Enter => evaluate(context, true)?, 21 | KeyEvent::Char(key_character) => character(context, key_character)?, 22 | KeyEvent::Backspace => erase(context)?, 23 | KeyEvent::Ctrl('c') => break, 24 | KeyEvent::Ctrl('d') => break, 25 | KeyEvent::Ctrl('l') => { 26 | let (_, row) = crossterm::cursor().pos()?; 27 | queue!(stdout(), ScrollUp(row), Up(row))?; 28 | } 29 | KeyEvent::Ctrl('u') => { 30 | context.expression.clear(); 31 | super::render::anchor_start(0)?; 32 | context.cursor_position = 0; 33 | 34 | evaluate(context, false)?; 35 | queue!(stdout(), Clear(ClearType::UntilNewLine))?; 36 | } 37 | KeyEvent::Ctrl('w') => erase_group(context)?, 38 | KeyEvent::Up => super::history::history_up(context)?, 39 | KeyEvent::Down => super::history::history_down(context)?, 40 | KeyEvent::Left if context.cursor_position >= 1 => { 41 | queue!(stdout(), Left(1))?; 42 | context.cursor_position -= 1; 43 | } 44 | KeyEvent::Right => { 45 | let count = context.expression.chars().count(); 46 | if context.cursor_position < count { 47 | queue!(stdout(), Right(1))?; 48 | context.cursor_position += 1; 49 | } 50 | } 51 | _ => (), 52 | } 53 | _ => (), 54 | } 55 | stdout().flush()?; 56 | } 57 | Ok(()) 58 | } 59 | 60 | fn character(context: &mut Context, character: char) -> Result { 61 | let index = context.expression.char_indices() 62 | .nth(context.cursor_position).map(|(index, _)| index) 63 | .unwrap_or(context.expression.len()); 64 | context.expression.insert(index, character); 65 | 66 | let slice = &context.expression[index..]; 67 | execute!(stdout(), SavePos, Output(slice.to_string()), 68 | ResetPos, Right(1))?; 69 | 70 | context.cursor_position += 1; 71 | evaluate(context, false) 72 | } 73 | 74 | fn erase(context: &mut Context) -> Result { 75 | match context.cursor_position >= 1 { 76 | false => Ok(()), 77 | true => { 78 | context.cursor_position -= 1; 79 | let index = context.expression.char_indices() 80 | .nth(context.cursor_position).map(|(index, _)| index).unwrap(); 81 | context.expression.remove(index); 82 | 83 | execute!(stdout(), Left(1), SavePos, Clear(ClearType::UntilNewLine), 84 | Output(context.expression[index..].to_string()), ResetPos)?; 85 | evaluate(context, false) 86 | } 87 | } 88 | } 89 | 90 | fn erase_group(context: &mut Context) -> Result { 91 | let end_index = context.expression.char_indices() 92 | .nth(context.cursor_position).map(|(index, _)| index) 93 | .unwrap_or(context.expression.len()); 94 | let start_index = context.expression[..end_index].char_indices().rev() 95 | .skip_while(|(_, character)| character.is_whitespace()) 96 | .skip_while(|(_, character)| !character.is_whitespace()) 97 | .take_while(|(_, character)| character.is_whitespace()) 98 | .last().map(|(index, _)| index).unwrap_or(0); 99 | 100 | let count = context.expression[start_index..end_index].chars().count(); 101 | match count > 0 { 102 | false => Ok(()), 103 | true => { 104 | context.cursor_position -= count; 105 | context.expression.replace_range(start_index..end_index, ""); 106 | execute!(stdout(), Left(count as u16), SavePos, Clear(ClearType::UntilNewLine), 107 | Output(context.expression[start_index..].to_string()), ResetPos)?; 108 | evaluate(context, false) 109 | } 110 | } 111 | } 112 | 113 | pub fn evaluate(context: &mut Context, store: bool) -> Result { 114 | let difference = (context.expression.chars().count() - context.cursor_position) as u16; 115 | queue!(stdout(), Right(difference), Clear(ClearType::UntilNewLine))?; 116 | 117 | let coalescence = match super::check::check(context)? { 118 | None => return render::anchor_start(context.cursor_position), 119 | Some(coalescence) => coalescence, 120 | }; 121 | 122 | if store { 123 | let expression = &mut context.expression; 124 | let expression = std::mem::replace(expression, String::new()); 125 | context.cursor_position = 0; 126 | context.push_history(expression); 127 | } 128 | 129 | let node = crate::parse::parse_root(coalescence); 130 | match node.evaluate(context) { 131 | Err(error) => { 132 | render::line_error(&error)?; 133 | render::line_break(false)?; 134 | } 135 | Ok(evaluation) => match store { 136 | true => { 137 | queue!(stdout(), Clear(ClearType::UntilNewLine))?; 138 | render::line_break(true)?; 139 | let index = context.push_value(evaluation); 140 | render::value_index(index); 141 | render::evaluation(evaluation, None); 142 | } 143 | false => { 144 | print!(" {}= ", Colored::Fg(Color::Green)); 145 | render::evaluation(evaluation, Some(Color::Green)); 146 | return render::anchor_start(context.cursor_position); 147 | } 148 | } 149 | } 150 | 151 | render::line_break(false)?; 152 | print!("{}", super::PROMPT.white().bold()); 153 | Ok(()) 154 | } 155 | 156 | -------------------------------------------------------------------------------- /src/interface/mod.rs: -------------------------------------------------------------------------------- 1 | pub use basic::{basic, evaluate, evaluate_direct}; 2 | pub use interface::interface; 3 | 4 | type Result = std::result::Result<(), Box>; 5 | 6 | pub const PROMPT: &str = ">> "; 7 | 8 | mod interface; 9 | mod history; 10 | mod render; 11 | mod check; 12 | mod basic; 13 | -------------------------------------------------------------------------------- /src/interface/render.rs: -------------------------------------------------------------------------------- 1 | use std::io::{stdout, Write}; 2 | 3 | use crossterm::*; 4 | 5 | use crate::error::Error; 6 | use crate::span::{Span, Spanned}; 7 | 8 | use super::Result; 9 | 10 | pub fn value_index(index: usize) { 11 | print!("{}{:x}{} ", "[".white().bold(), index, "]".white().bold()); 12 | } 13 | 14 | pub fn evaluation(evaluation: f64, colour: Option) { 15 | let colour = Colored::Fg(colour.unwrap_or(Color::Grey)); 16 | let exponentiation_range = 1e-3 < evaluation.abs() && evaluation.abs() < 1e9; 17 | match exponentiation_range || !evaluation.is_normal() { 18 | true => print!("{}{}", colour, evaluation), 19 | false => { 20 | let string = format!("{:e}", evaluation); 21 | print!("{}{}{}{}{}", colour, &string[..string.find('e').unwrap()], 22 | "e".white().bold(), colour, &string[string.find('e').unwrap() + 1..]); 23 | } 24 | } 25 | print!("{}", Colored::Fg(Color::Reset)); 26 | } 27 | 28 | pub fn line_error(error: &Spanned) -> Result { 29 | let Span(byte_start, byte_end) = error.span; 30 | let specific = "^".repeat(byte_end - byte_start).to_owned(); 31 | clear_buffer()?; 32 | 33 | let string = error.node.to_string(); 34 | queue!(stdout(), SavePos, Down(1), SetFg(Color::Red))?; 35 | anchor_start(byte_start)?; 36 | 37 | Ok(queue!(stdout(), Output(specific), Right(1), 38 | Output(string), SetFg(Color::Reset), ResetPos)?) 39 | } 40 | 41 | pub fn line_break(clear: bool) -> Result { 42 | match clear { 43 | true => clear_buffer(), 44 | false => buffer_line(), 45 | }?; 46 | 47 | let (_, row) = crossterm::cursor().pos()?; 48 | Ok(queue!(stdout(), Goto(0, row + 1))?) 49 | } 50 | 51 | pub fn anchor_start(offset: usize) -> Result { 52 | let (_, row) = crossterm::cursor().pos()?; 53 | Ok(queue!(stdout(), Goto((super::PROMPT.len() + offset) as u16, row))?) 54 | } 55 | 56 | pub fn clear_buffer() -> Result { 57 | buffer_line()?; 58 | Ok(queue!(stdout(), Down(1), Clear(ClearType::CurrentLine), Up(1))?) 59 | } 60 | 61 | pub fn buffer_line() -> Result { 62 | let (_, cursor_row) = crossterm::cursor().pos()?; 63 | let (_, terminal_rows) = crossterm::terminal().size()?; 64 | if cursor_row + 1 == terminal_rows { 65 | queue!(stdout(), ScrollUp(1), Up(1))?; 66 | } 67 | Ok(()) 68 | } 69 | -------------------------------------------------------------------------------- /src/item.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, PartialEq)] 2 | pub enum Function { 3 | Trigonometric(Trigonometric, AngleUnit), 4 | AbsoluteValue, 5 | SquareRoot, 6 | CubeRoot, 7 | NaturalLogarithm, 8 | BinaryLogarithm, 9 | DecimalLogarithm, 10 | UnaryMinus, 11 | } 12 | 13 | #[derive(Debug, PartialEq)] 14 | pub enum Trigonometric { 15 | Sine, 16 | Cosine, 17 | Tangent, 18 | InverseSine, 19 | InverseCosine, 20 | InverseTangent, 21 | } 22 | 23 | #[derive(Debug, PartialEq)] 24 | pub enum AngleUnit { 25 | Radians, 26 | Degrees, 27 | } 28 | 29 | impl AngleUnit { 30 | pub fn apply(&self, radians: f64) -> f64 { 31 | match self { 32 | AngleUnit::Radians => radians, 33 | AngleUnit::Degrees => radians.to_degrees(), 34 | } 35 | } 36 | 37 | pub fn radians(&self, value: f64) -> f64 { 38 | match self { 39 | AngleUnit::Radians => value, 40 | AngleUnit::Degrees => value.to_radians(), 41 | } 42 | } 43 | } 44 | 45 | #[derive(Debug, PartialEq)] 46 | pub enum Constant { 47 | E, 48 | Pi, 49 | } 50 | 51 | impl Constant { 52 | pub fn value(&self) -> f64 { 53 | use std::f64::consts; 54 | match self { 55 | Constant::E => consts::E, 56 | Constant::Pi => consts::PI, 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/lexer.rs: -------------------------------------------------------------------------------- 1 | use std::iter::Peekable; 2 | use std::str::CharIndices; 3 | 4 | use crate::error::Error; 5 | use crate::item::{AngleUnit, Constant, Function, Trigonometric}; 6 | use crate::span::{Span, Spanned}; 7 | use crate::token::{Operator, Token}; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct Lexer<'a> { 11 | string: &'a str, 12 | characters: Peekable>, 13 | last_coalesce: bool, 14 | byte_end: usize, 15 | } 16 | 17 | impl<'a> Lexer<'a> { 18 | pub fn new(string: &'a str) -> Lexer { 19 | Lexer { 20 | string, 21 | characters: string.char_indices().peekable(), 22 | last_coalesce: false, 23 | byte_end: string.len(), 24 | } 25 | } 26 | 27 | fn skip_whitespace(&mut self) { 28 | while let Some((_, character)) = self.characters.peek() { 29 | match character.is_whitespace() { 30 | true => self.characters.next(), 31 | false => return, 32 | }; 33 | } 34 | } 35 | 36 | fn number(&mut self) -> usize { 37 | let mut exponent_divider = false; 38 | while let Some((index, character)) = self.characters.peek() { 39 | match character { 40 | '.' | '_' => (), 41 | 'e' => { 42 | exponent_divider = true; 43 | self.characters.next(); 44 | continue; 45 | } 46 | '-' if exponent_divider => (), 47 | _ if character.is_digit(16) => (), 48 | _ => return *index, 49 | }; 50 | 51 | self.characters.next(); 52 | } 53 | self.byte_end 54 | } 55 | 56 | fn parse_number(&mut self, character: char, byte_start: usize) 57 | -> Result, Spanned> { 58 | let radix = match character { 59 | '0' => match self.characters.peek() { 60 | Some((_, prefix)) if prefix == &'x' => 16, 61 | Some((_, prefix)) if prefix == &'o' => 8, 62 | Some((_, prefix)) if prefix == &'b' => 2, 63 | _ => 10, 64 | } 65 | _ => 10, 66 | }; 67 | 68 | let number_start = match radix { 69 | 10 => byte_start, 70 | _ => { 71 | self.characters.next(); 72 | self.characters.next().map(|(index, _)| index) 73 | .unwrap_or(self.byte_end) 74 | } 75 | }; 76 | 77 | let byte_end = self.number(); 78 | let string = &self.string[number_start..byte_end] 79 | .matches(|c| c != '_').collect::(); 80 | let span = Span(byte_start, byte_end); 81 | 82 | let error = Spanned::new(Error::InvalidTerminal, span); 83 | match radix { 84 | 10 => string.parse::().map_err(|_| error), 85 | _ => i64::from_str_radix(string, radix).map_err(|_| error) 86 | .map(|terminal| terminal as f64), 87 | }.map(|terminal| Spanned::new(Token::Terminal(terminal), span)) 88 | } 89 | 90 | fn take_coalesce(&mut self) -> (usize, usize) { 91 | let mut counter = 1; 92 | while let Some((index, character)) = self.characters.peek() { 93 | match character == &';' { 94 | true => self.characters.next(), 95 | false => return (*index, counter), 96 | }; 97 | counter += 1; 98 | } 99 | (self.byte_end, counter) 100 | } 101 | 102 | fn take_identifier(&mut self) -> usize { 103 | while let Some((index, character)) = self.characters.peek() { 104 | let invalid_character = character.is_whitespace() || 105 | character.is_ascii_punctuation(); 106 | match !['$', '\''].contains(character) && invalid_character { 107 | false => self.characters.next(), 108 | true => return *index, 109 | }; 110 | } 111 | self.byte_end 112 | } 113 | } 114 | 115 | impl<'a> Iterator for Lexer<'a> { 116 | type Item = Result, Spanned>; 117 | 118 | fn next(&mut self) -> Option { 119 | let (byte_start, character) = self.characters.next()?; 120 | if character.is_whitespace() { 121 | self.skip_whitespace(); 122 | return self.next(); 123 | } 124 | 125 | if character.is_digit(10) { 126 | return Some(self.parse_number(character, byte_start)); 127 | } else if character == ';' { 128 | let (byte_end, counter) = self.take_coalesce(); 129 | return Some(Ok(Spanned::new(Token::Coalesce(counter), 130 | Span(byte_start, byte_end)))); 131 | } else if character == '$' { 132 | let byte_end = self.take_identifier(); 133 | let token = Token::Variable(self.string[byte_start + 1..byte_end].to_owned()); 134 | return Some(Ok(Spanned::new(token, Span(byte_start, byte_end)))); 135 | } 136 | 137 | if !character.is_ascii_punctuation() { 138 | let byte_end = self.take_identifier(); 139 | let span = Span(byte_start, byte_end); 140 | let mut slice = &self.string[byte_start..byte_end]; 141 | let token = Spanned::new(match slice { 142 | "abs" => Token::Function(Function::AbsoluteValue), 143 | "sqrt" => Token::Function(Function::SquareRoot), 144 | "cbrt" => Token::Function(Function::CubeRoot), 145 | "ln" => Token::Function(Function::NaturalLogarithm), 146 | "log2" => Token::Function(Function::BinaryLogarithm), 147 | "log10" => Token::Function(Function::DecimalLogarithm), 148 | "e" => Token::Constant(Constant::E), 149 | "pi" => Token::Constant(Constant::Pi), 150 | _ => { 151 | let mut unit = AngleUnit::Radians; 152 | if let Some((index, '\'')) = slice.char_indices().last() { 153 | unit = AngleUnit::Degrees; 154 | slice = &slice[..index]; 155 | } 156 | 157 | Token::Function(Function::Trigonometric(match slice { 158 | "sin" => Trigonometric::Sine, 159 | "cos" => Trigonometric::Cosine, 160 | "tan" => Trigonometric::Tangent, 161 | "asin" => Trigonometric::InverseSine, 162 | "acos" => Trigonometric::InverseCosine, 163 | "atan" => Trigonometric::InverseTangent, 164 | _ => return Some(Err(Spanned::new(Error::InvalidItem, span))), 165 | }, unit)) 166 | } 167 | }, span); 168 | return Some(Ok(token)); 169 | } 170 | 171 | let span = Span(byte_start, self.characters.peek() 172 | .map(|(index, _)| *index).unwrap_or(self.byte_end)); 173 | let token = Spanned::new(match character { 174 | '(' => Token::ParenthesisOpen, 175 | ')' => Token::ParenthesisClose, 176 | '+' => Token::Operator(Operator::Add), 177 | '-' => Token::Operator(Operator::Minus), 178 | '*' => Token::Operator(Operator::Multiply), 179 | '/' => Token::Operator(Operator::Divide), 180 | '%' => Token::Operator(Operator::Modulo), 181 | '^' => Token::Operator(Operator::Power), 182 | _ => return Some(Err(Spanned::new(Error::InvalidCharacter(character), span))), 183 | }, span); 184 | Some(Ok(token)) 185 | } 186 | } 187 | 188 | #[cfg(test)] 189 | mod tests { 190 | use super::*; 191 | 192 | #[test] 193 | fn test_tokenize() { 194 | let string = "(1 + 2) / 3 * -54; ;"; 195 | let tokens: Result, _> = Lexer::new(string) 196 | .map(|token| token.map(|token| token.node)).collect(); 197 | assert_eq!(tokens.unwrap(), &[Token::ParenthesisOpen, 198 | Token::Terminal(1.0), Token::Operator(Operator::Add), Token::Terminal(2.0), 199 | Token::ParenthesisClose, Token::Operator(Operator::Divide), Token::Terminal(3.0), 200 | Token::Operator(Operator::Multiply), Token::Operator(Operator::Minus), 201 | Token::Terminal(54.0), Token::Coalesce(1), Token::Coalesce(1)]); 202 | } 203 | 204 | #[test] 205 | fn test_numerical_format() { 206 | let string = "10 + -10.0 0x0a 0b1010 0o12 + -1e1 + 1_023_568"; 207 | let tokens: Result, _> = Lexer::new(string) 208 | .map(|token| token.map(|token| token.node)).collect(); 209 | assert_eq!(tokens.unwrap(), &[Token::Terminal(10.0), Token::Operator(Operator::Add), 210 | Token::Operator(Operator::Minus), Token::Terminal(10.0), Token::Terminal(10.0), 211 | Token::Terminal(10.0), Token::Terminal(10.0), Token::Operator(Operator::Add), 212 | Token::Operator(Operator::Minus), Token::Terminal(10.0), Token::Operator(Operator::Add), 213 | Token::Terminal(1023568.0)]); 214 | } 215 | 216 | #[test] 217 | fn test_identifier() { 218 | let string = "$ $0 $$ $identifier"; 219 | let tokens: Result, _> = Lexer::new(string) 220 | .map(|token| token.map(|token| token.node)).collect(); 221 | assert_eq!(tokens.unwrap(), &[Token::Variable("".to_owned()), 222 | Token::Variable("0".to_owned()), Token::Variable("$".to_owned()), 223 | Token::Variable("identifier".to_owned())]); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod span; 2 | mod lexer; 3 | mod error; 4 | mod token; 5 | mod coalesce; 6 | mod parse; 7 | mod node; 8 | mod item; 9 | mod interface; 10 | mod coalescence; 11 | mod context; 12 | #[cfg(test)] 13 | mod tests; 14 | 15 | fn main() -> Result<(), Box> { 16 | let argument = std::env::args().nth(1); 17 | match argument.as_ref().map(String::as_str) { 18 | Some("-b") | Some("--basic") => interface::basic()?, 19 | Some("-e") | Some("--evaluate") => interface::evaluate_direct()?, 20 | _ => if let Err(_) = interface::interface() { 21 | interface::basic()?; 22 | }, 23 | } 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /src/node.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::error::Error; 3 | use crate::item::{Function, Trigonometric}; 4 | use crate::span::Spanned; 5 | use crate::token::Operator; 6 | 7 | #[derive(Debug)] 8 | pub enum Node { 9 | Terminal(f64), 10 | Variable(String), 11 | Function(Function, Box>), 12 | Operator(Spanned, Box>, Box>), 13 | } 14 | 15 | impl Spanned { 16 | pub fn evaluate(&self, context: &Context) -> Result> { 17 | Ok(match &self.node { 18 | Node::Terminal(terminal) => *terminal, 19 | Node::Variable(variable) => context.variable(variable) 20 | .map_err(|error| Spanned::new(error, self.span))?, 21 | Node::Function(function, node) => { 22 | let value = node.evaluate(context)?; 23 | match function { 24 | Function::Trigonometric(function, unit) => { 25 | use Trigonometric::*; 26 | match function { 27 | Sine => unit.radians(value).sin(), 28 | Cosine => unit.radians(value).cos(), 29 | Tangent => unit.radians(value).tan(), 30 | InverseSine => unit.apply(value.asin()), 31 | InverseCosine => unit.apply(value.acos()), 32 | InverseTangent => unit.apply(value.atan()), 33 | } 34 | } 35 | Function::AbsoluteValue => value.abs(), 36 | Function::SquareRoot => match value >= 0.0 { 37 | false => return Err(Spanned::new(Error::NegativeRoot, node.span)), 38 | true => value.sqrt(), 39 | }, 40 | Function::CubeRoot => match value >= 0.0 { 41 | false => return Err(Spanned::new(Error::NegativeRoot, node.span)), 42 | true => value.cbrt(), 43 | }, 44 | Function::NaturalLogarithm => value.ln(), 45 | Function::BinaryLogarithm => value.log2(), 46 | Function::DecimalLogarithm => value.log10(), 47 | Function::UnaryMinus => -value, 48 | } 49 | } 50 | Node::Operator(operator, left_node, right_node) => { 51 | let left = left_node.evaluate(context)?; 52 | let right = right_node.evaluate(context)?; 53 | match operator.node { 54 | Operator::Add => left + right, 55 | Operator::Minus => left - right, 56 | Operator::Multiply => left * right, 57 | Operator::Divide => match right != 0.0 { 58 | false => return Err(Spanned::new(Error::ZeroDivision, right_node.span)), 59 | true => left / right 60 | } 61 | Operator::Modulo => left % right, 62 | Operator::Power => left.powf(right), 63 | } 64 | } 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::coalescence::Coalescence; 2 | use crate::item::Function; 3 | use crate::node::Node; 4 | use crate::span::{Span, Spanned}; 5 | use crate::token::Operator; 6 | 7 | #[derive(Debug)] 8 | enum ParserOperator { 9 | Operator(Spanned), 10 | Function(Spanned), 11 | } 12 | 13 | impl ParserOperator { 14 | pub fn precedence(&self) -> usize { 15 | match self { 16 | ParserOperator::Operator(operator) => operator.node.precedence(), 17 | ParserOperator::Function(_) => usize::max_value(), 18 | } 19 | } 20 | } 21 | 22 | pub fn parse_root(coalescence: Coalescence) -> Spanned { 23 | let nodes = &mut Vec::new(); 24 | parse(coalescence, &mut Vec::new(), 0, nodes); 25 | assert_eq!(nodes.len(), 1); 26 | nodes.pop().unwrap() 27 | } 28 | 29 | fn parse(coalescence: Coalescence, operators: &mut Vec, 30 | state: usize, nodes: &mut Vec>) { 31 | match coalescence { 32 | Coalescence::Terminal(terminal) => 33 | nodes.push(Spanned::new(Node::Terminal(terminal.node), terminal.span)), 34 | Coalescence::Variable(variable) => 35 | nodes.push(Spanned::new(Node::Variable(variable.node), variable.span)), 36 | Coalescence::Function(function) => 37 | operators.push(ParserOperator::Function(function)), 38 | Coalescence::Operator(operator) => { 39 | while let Some(stack_operator) = operators.last() { 40 | match stack_operator.precedence() >= operator.node.precedence() { 41 | true if operators.len() > state => construct(operators, nodes), 42 | _ => break, 43 | } 44 | } 45 | operators.push(ParserOperator::Operator(operator)); 46 | } 47 | Coalescence::Multiple(coalesces) => { 48 | let operator_state = operators.len(); 49 | coalesces.into_iter().for_each(|coalescence| 50 | parse(coalescence, operators, operator_state, nodes)); 51 | 52 | assert!(operators.len() >= operator_state); 53 | while operators.len() > operator_state { 54 | construct(operators, nodes); 55 | } 56 | } 57 | } 58 | } 59 | 60 | fn construct(operators: &mut Vec, nodes: &mut Vec>) { 61 | let operator = operators.pop().unwrap(); 62 | match operator { 63 | ParserOperator::Operator(operator) => { 64 | let right = nodes.pop().unwrap(); 65 | let left = nodes.pop().unwrap(); 66 | let span = Span(left.span.byte_start(), right.span.byte_end()); 67 | let node = Node::Operator(operator, Box::new(left), Box::new(right)); 68 | nodes.push(Spanned::new(node, span)) 69 | } 70 | ParserOperator::Function(function) => { 71 | let node = nodes.pop().unwrap(); 72 | let span = Span(function.span.byte_start(), node.span.byte_end()); 73 | let node = Node::Function(function.node, Box::new(node)); 74 | nodes.push(Spanned::new(node, span)) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/span.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(Copy, Clone)] 4 | pub struct Span(pub usize, pub usize); 5 | 6 | impl Span { 7 | pub fn byte_start(&self) -> usize { 8 | let Span(byte_start, _) = self; 9 | *byte_start 10 | } 11 | 12 | pub fn byte_end(&self) -> usize { 13 | let Span(_, byte_end) = self; 14 | *byte_end 15 | } 16 | } 17 | 18 | impl fmt::Debug for Span { 19 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 20 | let Span(byte_start, byte_end) = self; 21 | write!(f, "Span({}, {})", byte_start, byte_end) 22 | } 23 | } 24 | 25 | #[derive(Debug, Clone)] 26 | pub struct Spanned { 27 | pub node: T, 28 | pub span: Span, 29 | } 30 | 31 | impl Spanned { 32 | pub fn new(node: T, span: Span) -> Self { 33 | Spanned { node, span } 34 | } 35 | 36 | pub fn map(self, node: R) -> Spanned { 37 | Spanned::new(node, self.span) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/tests/evaluation.rs: -------------------------------------------------------------------------------- 1 | use crate::context::Context; 2 | use crate::error::Error; 3 | 4 | #[test] 5 | fn test_arithmetic() { 6 | assert_eq!(evaluate("1 + 2"), Ok(3.0)); 7 | assert_eq!(evaluate("1 - 2"), Ok(-1.0)); 8 | assert_eq!(evaluate("1 * 2"), Ok(2.0)); 9 | assert_eq!(evaluate("1 / 2"), Ok(0.5)); 10 | assert_eq!(evaluate("10 ^ -2"), Ok(0.01)); 11 | } 12 | 13 | #[test] 14 | fn test_unary_minus() { 15 | assert_eq!(evaluate("-1"), Ok(-1.0)); 16 | assert_eq!(evaluate("1 - -1"), Ok(2.0)); 17 | assert_eq!(evaluate("1 --- 1"), Ok(0.0)); 18 | assert_eq!(evaluate("-1 -- -1"), Ok(-2.0)); 19 | } 20 | 21 | #[test] 22 | fn test_functions() { 23 | assert_eq!(evaluate("abs -1"), Ok(1.0)); 24 | assert_eq!(evaluate("sin asin 1"), Ok(1.0)); 25 | assert_eq!(evaluate("sin' asin' 1"), Ok(1.0)); 26 | assert_eq!(evaluate("sqrt -1 + 1;;"), Ok(0.0)); 27 | } 28 | 29 | #[test] 30 | fn test_implicit() { 31 | assert_eq!(evaluate("1 (2)"), Ok(2.0)); 32 | assert_eq!(evaluate("1 (2 (3))"), Ok(6.0)); 33 | assert_eq!(evaluate("1 (2) (3)"), Ok(6.0)); 34 | assert_eq!(evaluate("(1) 2 + 3"), Ok(5.0)); 35 | assert_eq!(evaluate("abs -1 (2)"), Ok(2.0)); 36 | assert_eq!(evaluate("(abs -1) 2"), Ok(2.0)); 37 | } 38 | 39 | fn evaluate(expression: &str) -> Result { 40 | let mut context = &mut Context::default(); 41 | context.expression = expression.to_owned(); 42 | crate::interface::evaluate(context) 43 | .map_err(|error| error.node) 44 | } 45 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod evaluation; 2 | -------------------------------------------------------------------------------- /src/token.rs: -------------------------------------------------------------------------------- 1 | use crate::item::{Constant, Function}; 2 | 3 | #[derive(Debug, PartialEq)] 4 | pub enum Token { 5 | Terminal(f64), 6 | Variable(String), 7 | Operator(Operator), 8 | Function(Function), 9 | Constant(Constant), 10 | ParenthesisOpen, 11 | ParenthesisClose, 12 | Coalesce(usize), 13 | } 14 | 15 | #[derive(Debug, PartialEq, Copy, Clone)] 16 | pub enum Operator { 17 | Add, 18 | Minus, 19 | Multiply, 20 | Divide, 21 | Modulo, 22 | Power, 23 | } 24 | 25 | impl Operator { 26 | pub fn precedence(&self) -> usize { 27 | match self { 28 | Operator::Add | Operator::Minus => 0, 29 | Operator::Multiply | Operator::Divide | Operator::Modulo => 1, 30 | Operator::Power => 2, 31 | } 32 | } 33 | } 34 | --------------------------------------------------------------------------------