├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── benches.rs ├── grammar └── src ├── bin └── main.rs ├── expr.rs ├── interpreter.rs ├── lib.rs ├── parser.rs └── tokenizer.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | /.vs 4 | /.idea 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## 3.0 - 2024-03-31 8 | ### Added 9 | #### In the executable 10 | * Help command list 11 | * `vars` command shows active variables and functions. 12 | 13 | ### In the library 14 | * Implement the `Num` trait for all signed integer primitives: `i8`, `i16`, `i32`, `i64`, `i128`, `isize`, and floats: `f32` and `f64`. 15 | 16 | ### Changed 17 | * Rewrote *everything*. 18 | * Lexer and `Token` architecture. `Token` now includes data about where it was located in the input, and how many characters it spans, which is useful for errors. 19 | * Parser and `Expr` usage. Overall code cleanup for the parser. Now uses a lookahead of 2 to solve ambiguity in parsing. See grammar. 20 | * `ParseError` is now descriptive, including the position and length of the problem, and even sometimes providing the offending token. 21 | * `Computer` became `Interpreter`. A lot of changes were made to the interpreter, compared to the old `Computer` that you should check out when migrating. 22 | * Some semantic expressions like absolute value `|x|` and factorial `x!` are now translated to `abs(x)` and `factorial(x)`, respectively. 23 | * The entire system still remains generic over which type of number is used, but I have simplified and extended the trait `Num` which a type must still implement to be used. 24 | * Update peekmore dependency 1.0.0 -> 1.3.0. 25 | 26 | ### Removed 27 | * `ans` variable. 28 | * Global `eval` function and `EvalError` tagged enum. The "simplistic" interface was really quite complex and made things pretty complicated. 29 | 30 | ### Fixed 31 | * Some bugs in the grammar that caused seemingly ordinary expressions to produce false results. 32 | * Determining at runtime whether `x(5)` is a function `x` with an argument `5` or a variable `x` times `5`. 33 | * Functions were accidentally defined as the trait `Fn`, embarrassingly. I've updated functions, so they are now actually usable. 34 | 35 | ## 2.0 - 2019-06-21 36 | ### Added 37 | * Real named functions! Functions are no longer tokens, and can now be created in a `Computer`, similar to variables. 38 | ```rust 39 | let mut map = HashMap:: f64>::new(); 40 | map.insert("sqrt".to_owned(), &|n| n.sqrt()); 41 | ``` 42 | * RSC is fully generic, now! Types that can support addition, subtraction, and a couple functions necessary in the `Num` trait can be lexed, parsed, and computed with no changes to the RSC source code. 43 | * Getting the previous answer with the new `ans` variable. `ans` does not exist until you've run a calculation on a Computer already. 44 | * Factorial: `5! = 120` 45 | 46 | ## [1.2.1] - 2017-06-20 47 | ### Removed 48 | * Tests from lib.rs removed so it can compile on stable compiler branches. 49 | 50 | *Versions prior to 1.2.1 had no changelog recordings, unfortunately.* 51 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ansi_term" 7 | version = "0.12.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 10 | dependencies = [ 11 | "winapi", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi", 21 | "libc", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "bitflags" 27 | version = "1.3.2" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 30 | 31 | [[package]] 32 | name = "clap" 33 | version = "2.34.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 36 | dependencies = [ 37 | "ansi_term", 38 | "atty", 39 | "bitflags", 40 | "strsim", 41 | "textwrap", 42 | "unicode-width", 43 | "vec_map", 44 | ] 45 | 46 | [[package]] 47 | name = "colored" 48 | version = "2.1.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" 51 | dependencies = [ 52 | "lazy_static", 53 | "windows-sys", 54 | ] 55 | 56 | [[package]] 57 | name = "heck" 58 | version = "0.3.3" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 61 | dependencies = [ 62 | "unicode-segmentation", 63 | ] 64 | 65 | [[package]] 66 | name = "hermit-abi" 67 | version = "0.1.19" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 70 | dependencies = [ 71 | "libc", 72 | ] 73 | 74 | [[package]] 75 | name = "lazy_static" 76 | version = "1.5.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 79 | 80 | [[package]] 81 | name = "libc" 82 | version = "0.2.161" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" 85 | 86 | [[package]] 87 | name = "peekmore" 88 | version = "1.3.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "9163e1259760e83d528d1b3171e5100c1767f10c52e1c4d6afad26e63d47d758" 91 | 92 | [[package]] 93 | name = "proc-macro-error" 94 | version = "1.0.4" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 97 | dependencies = [ 98 | "proc-macro-error-attr", 99 | "proc-macro2", 100 | "quote", 101 | "syn", 102 | "version_check", 103 | ] 104 | 105 | [[package]] 106 | name = "proc-macro-error-attr" 107 | version = "1.0.4" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 110 | dependencies = [ 111 | "proc-macro2", 112 | "quote", 113 | "version_check", 114 | ] 115 | 116 | [[package]] 117 | name = "proc-macro2" 118 | version = "1.0.89" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 121 | dependencies = [ 122 | "unicode-ident", 123 | ] 124 | 125 | [[package]] 126 | name = "quote" 127 | version = "1.0.37" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 130 | dependencies = [ 131 | "proc-macro2", 132 | ] 133 | 134 | [[package]] 135 | name = "rsc" 136 | version = "3.0.0" 137 | dependencies = [ 138 | "colored", 139 | "peekmore", 140 | "structopt", 141 | ] 142 | 143 | [[package]] 144 | name = "strsim" 145 | version = "0.8.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 148 | 149 | [[package]] 150 | name = "structopt" 151 | version = "0.3.26" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 154 | dependencies = [ 155 | "clap", 156 | "lazy_static", 157 | "structopt-derive", 158 | ] 159 | 160 | [[package]] 161 | name = "structopt-derive" 162 | version = "0.4.18" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 165 | dependencies = [ 166 | "heck", 167 | "proc-macro-error", 168 | "proc-macro2", 169 | "quote", 170 | "syn", 171 | ] 172 | 173 | [[package]] 174 | name = "syn" 175 | version = "1.0.109" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 178 | dependencies = [ 179 | "proc-macro2", 180 | "quote", 181 | "unicode-ident", 182 | ] 183 | 184 | [[package]] 185 | name = "textwrap" 186 | version = "0.11.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 189 | dependencies = [ 190 | "unicode-width", 191 | ] 192 | 193 | [[package]] 194 | name = "unicode-ident" 195 | version = "1.0.13" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 198 | 199 | [[package]] 200 | name = "unicode-segmentation" 201 | version = "1.12.0" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 204 | 205 | [[package]] 206 | name = "unicode-width" 207 | version = "0.1.14" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 210 | 211 | [[package]] 212 | name = "vec_map" 213 | version = "0.8.2" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 216 | 217 | [[package]] 218 | name = "version_check" 219 | version = "0.9.5" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 222 | 223 | [[package]] 224 | name = "winapi" 225 | version = "0.3.9" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 228 | dependencies = [ 229 | "winapi-i686-pc-windows-gnu", 230 | "winapi-x86_64-pc-windows-gnu", 231 | ] 232 | 233 | [[package]] 234 | name = "winapi-i686-pc-windows-gnu" 235 | version = "0.4.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 238 | 239 | [[package]] 240 | name = "winapi-x86_64-pc-windows-gnu" 241 | version = "0.4.0" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 244 | 245 | [[package]] 246 | name = "windows-sys" 247 | version = "0.48.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 250 | dependencies = [ 251 | "windows-targets", 252 | ] 253 | 254 | [[package]] 255 | name = "windows-targets" 256 | version = "0.48.5" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 259 | dependencies = [ 260 | "windows_aarch64_gnullvm", 261 | "windows_aarch64_msvc", 262 | "windows_i686_gnu", 263 | "windows_i686_msvc", 264 | "windows_x86_64_gnu", 265 | "windows_x86_64_gnullvm", 266 | "windows_x86_64_msvc", 267 | ] 268 | 269 | [[package]] 270 | name = "windows_aarch64_gnullvm" 271 | version = "0.48.5" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 274 | 275 | [[package]] 276 | name = "windows_aarch64_msvc" 277 | version = "0.48.5" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 280 | 281 | [[package]] 282 | name = "windows_i686_gnu" 283 | version = "0.48.5" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 286 | 287 | [[package]] 288 | name = "windows_i686_msvc" 289 | version = "0.48.5" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 292 | 293 | [[package]] 294 | name = "windows_x86_64_gnu" 295 | version = "0.48.5" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 298 | 299 | [[package]] 300 | name = "windows_x86_64_gnullvm" 301 | version = "0.48.5" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 304 | 305 | [[package]] 306 | name = "windows_x86_64_msvc" 307 | version = "0.48.5" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 310 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rsc" 3 | version = "3.0.0" 4 | edition = "2018" 5 | authors = ["Luke I. Wilson "] 6 | description = "A fast calculator for solving scientific and algebraic math equations in strings." 7 | repository = "https://github.com/fivemoreminix/rsc" 8 | readme = "README.md" 9 | keywords = ["scientific", "calculator", "parser", "expression"] 10 | categories = ["command-line-utilities", "parsing", "science"] 11 | license = "MIT" 12 | 13 | [lib] 14 | path = "src/lib.rs" 15 | 16 | [[bin]] 17 | name = "rsc" 18 | path = "src/bin/main.rs" 19 | required-features = ["executable"] 20 | 21 | [features] 22 | executable = ["structopt", "colored"] 23 | 24 | [dependencies] 25 | peekmore = "^1.3.0" 26 | #num = "^0.4.0" 27 | # dependencies for the runnable version (feature "executable") 28 | structopt = { version = "^0.3.26", optional = true } 29 | colored = { version = "^2.1", optional = true } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Luke Wilson 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 | RSC, the Calculator for Rust Code 2 | ================================= 3 | ![](https://img.shields.io/crates/l/rsc.svg) ![](https://img.shields.io/badge/status-stable-blue.svg) 4 | 5 | **New**: crate updated to 3.0, read the [Changelog](CHANGELOG.md). 6 | 7 | **RSC is a handwritten scientific calculator for interpreting equations inside strings.** RSC is designed to do a single 8 | thing very well, enabling anyone to extend it with more features. 9 | 10 | RSC intends to beat Wirth's Law. **Therefore, RSC will not receive many additions.** It will still receive updates with 11 | relation to efficiency. 12 | 13 | ## Library 14 | ```rust 15 | use rsc::{tokenize, parse, Interpreter}; 16 | 17 | // Maybe you write a wrapper function 18 | fn evaluate(input: &str, interpreter: &mut Interpreter) -> Result { 19 | // You have to call each function in the pipeline, but this gives you the most 20 | // control over error handling and performance. 21 | match tokenize(input) { // Step 1: splits input into symbols, words, and numbers 22 | Ok(tokens) => match parse(&tokens) { // Step 2: builds an Expr using tokens 23 | Ok(expr) => match interpreter.eval(&expr) { // Step 3: interprets the Expr 24 | Ok(result) => println!("{}", result), 25 | Err(interpret_error) => eprintln!("{:?}", interpret_error), 26 | }, 27 | Err(parse_error) => eprintln!("{:?}", parse_error), 28 | }, 29 | Err(tokenize_error) => eprintln!("{:?}", tokenize_error), 30 | } 31 | } 32 | 33 | fn main() { 34 | // Constructs an f64 interpreter with included variables 35 | let mut interpreter = Interpreter::default(); 36 | 37 | evaluate("5^2", &mut interpreter); // prints "25" 38 | evaluate("x = 3", &mut interpreter); // prints "3" 39 | evaluate("x(3) + 1", &mut interpreter); // prints "10" 40 | } 41 | ``` 42 | 43 | Variables are stored in the `Interpreter`: 44 | ```rust 45 | use rsc::{tokenize, parse, Interpreter, Variant, InterpretError}; 46 | 47 | // assume you still had your evaluate function above 48 | 49 | fn main() { 50 | // Create a completely empty interpreter for f64 calculations 51 | let mut i = Interpreter::::new(); 52 | 53 | // Create some variables 54 | i.set_var(String::from("pi"), Variant::Num(std::f64::consts::PI)); 55 | i.set_var(String::from("double"), Variant::Function(|name, args| { 56 | if args.len() < 1 { 57 | Err(InterpretError::TooFewArgs(name, 1)) 58 | } else if args.len() > 1 { 59 | Err(InterpretError::TooManyArgs(name, 1)) 60 | } else { 61 | Ok(args[0] * 2) // get the only argument and double it 62 | } 63 | })); 64 | 65 | evaluate("double(pi)", &mut i); // prints "6.283185307179586" 66 | } 67 | ``` 68 | 69 | Because it can be redundant checking that functions received the correct number of arguments (if you wish to do so at all), 70 | I made a helper function called `ensure_arg_count`. The above function redefined: 71 | 72 | ```rust 73 | use rsc::ensure_arg_count; 74 | 75 | i.set_var(String::from("double"), Variant::Function(|name, args| { 76 | // return Err if args are not within the min and max count 77 | ensure_arg_count(1, 1, args.len(), name)?; 78 | Ok(args[0] * 2) 79 | })); 80 | ``` 81 | 82 | ## Executable 83 | ### First you might need to build RSC as an executable 84 | ```shell 85 | cargo build --release --features=executable 86 | ``` 87 | The `executable` feature is required to tell the crate to bring in certain dependencies only for the executable version, for colors in the terminal and argument parsing. 88 | 89 | ### Usage 90 | ```shell 91 | RSC interactive expression interpreter. 92 | Try "help" for commands and examples. 93 | >sqrt(15+3) 94 | :4.242640687119285 95 | >:square root 96 | >sqrt(15, 3) 97 | Function "sqrt" received more than the maximum 1 argument. 98 | > |-5| 99 | :5 100 | >abs(-5) 101 | :5 102 | >sqrt(4)(2) 103 | ^ UnexpectedToken(Token { value: Symbol(LP), span: 7..8 }) 104 | >(sqrt(4))(2) 105 | :4 106 | >x = 1.24 107 | :1.24 108 | >x(4) 109 | :4.96 110 | >vars 111 | factorial(..) 112 | sqrt(..) 113 | abs(..) 114 | x = 1.24 115 | e = 2.718281828459045 116 | pi = 3.141592653589793 117 | tau = 6.283185307179586 118 | ``` 119 | 120 | Expressions can be passed to rsc directly: 121 | ```shell 122 | $ rsc "12/sqrt(128)" 123 | 1.0606601717798212 124 | ``` 125 | 126 | There are various flags you can pass. Try: 127 | ```shell 128 | rsc -tev 129 | ``` 130 | 131 | ```shell 132 | $ rsc -h 133 | rsc 3.0.0 134 | A scientific calculator for the terminal. 135 | 136 | USAGE: 137 | rsc [FLAGS] [expr] 138 | 139 | FLAGS: 140 | -e, --expr Prints the expression tree 141 | -h, --help Prints help information 142 | --no-color Prevents colored text 143 | -t, --tokens Prints the tokens 144 | -v, --vars Prints variable map 145 | -V, --version Prints version information 146 | 147 | ARGS: 148 | 149 | ``` 150 | 151 | ## Notes About Performance 152 | * The lexer is iterative and can easily be optimized. 153 | * The parser is an LL(2) recursive-descent parser, and that's the simplest, most brute-force parsing solution I came up with. It's easy to understand and maintain, but not the most efficient. The parser is currently the slowest of the 3 phases. 154 | * The `Interpreter::eval` function uses recursion for simplicity. Removing the recursion could prevent unnecessary pushing and popping of the frame pointer, and enable better caching, providing better performance. 155 | * Performance improvement PRs are very much welcomed and probably easy! 156 | 157 | ## Stability 158 | RSC will not have any major changes to its syntax. It will remain consistent for a long time. It is up to forkers to make different flavors of RSC. It will also forever keep the same open-source permissions. 159 | 160 | ## License 161 | RSC is MIT licensed. RSC will always remain free to modify and use without attribution. 162 | -------------------------------------------------------------------------------- /benches/benches.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | 5 | use rsc::{parse, tokenize, Interpreter, Variant}; 6 | use test::{black_box, Bencher}; 7 | 8 | const SHORT_STR: &str = "5.324 * 54(pad)"; 9 | const LONG_STR: &str = "0.999998543 / sqrt(54 ^ (x(3))) % applesauce + bees"; 10 | const FUNCTIONS_VARS: &str = "abs(5) + x(3) + abs(x(2)) + sqrt(4)"; 11 | 12 | macro_rules! tokenizer_bench { 13 | ($name:ident, $input:expr) => { 14 | #[bench] 15 | fn $name(b: &mut Bencher) { 16 | b.iter(|| tokenize::(black_box($input))); 17 | } 18 | }; 19 | } 20 | 21 | macro_rules! parser_bench { 22 | ($name:ident, $input:expr) => { 23 | #[bench] 24 | fn $name(b: &mut Bencher) { 25 | let tokens = tokenize::($input).unwrap(); 26 | b.iter(|| parse(black_box(&tokens))) 27 | } 28 | }; 29 | } 30 | 31 | macro_rules! eval_bench { 32 | ($name:ident, $input:expr) => { 33 | #[bench] 34 | fn $name(b: &mut Bencher) { 35 | let tokens = tokenize($input).unwrap(); 36 | let expr = parse(&tokens).unwrap(); 37 | let mut i = Interpreter::default(); 38 | i.set_var(String::from("pad"), Variant::Num(5.0)); 39 | i.set_var(String::from("x"), Variant::Num(2.0)); 40 | i.set_var(String::from("applesauce"), Variant::Num(1.0)); 41 | i.set_var(String::from("bees"), Variant::Num(1.0)); 42 | b.iter(|| { 43 | i.eval(black_box(&expr)).unwrap(); 44 | }) 45 | } 46 | }; 47 | } 48 | 49 | tokenizer_bench!(tokenizer_short_expr, SHORT_STR); 50 | tokenizer_bench!(tokenizer_long_expr, LONG_STR); 51 | tokenizer_bench!(tokenizer_function_vars, FUNCTIONS_VARS); 52 | 53 | parser_bench!(parser_short_expr, SHORT_STR); 54 | parser_bench!(parser_long_expr, LONG_STR); 55 | parser_bench!(parser_function_vars, FUNCTIONS_VARS); 56 | 57 | eval_bench!(eval_short_expr, SHORT_STR); 58 | eval_bench!(eval_long_expr, LONG_STR); 59 | eval_bench!(eval_function_vars, FUNCTIONS_VARS); 60 | -------------------------------------------------------------------------------- /grammar: -------------------------------------------------------------------------------- 1 | expr = eq_expr ; 2 | 3 | eq_expr = add_expr, { "=", add_expr } ; 4 | add_expr = mul_expr, { ("+" | "-"), mul_expr } ; 5 | mul_expr = pow_expr, { ("*" | "/" | "%"), pow_expr } ; 6 | pow_expr = parentheses_mul_expr, { "^", factor } ; 7 | 8 | parentheses_mul_expr = func_or_var_mul_expr | ( factorial_expr, { "(", expr, ")" } ) ; 9 | func_or_var_mul_expr = identifier, "(", [ expr { ",", expr } ], ")" ; (* need lookahead 2 *) 10 | 11 | factorial_expr = factor, { "!" } ; 12 | factor = "(", expr, ")" 13 | | "|", expr, "|" 14 | | "-", expr 15 | | number 16 | | identifier ; 17 | 18 | identifier = alpha, { alpha | digit } ; 19 | alpha = "A".."Z" | "a".."z" ; 20 | 21 | digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; 22 | (* number = 52 or .14 or -65535 or -.256 or -340.430 etc *) 23 | number = [ "-" ], ( digit, { digit }, [ ".", { digit } ] ) | ( ".", digit, { digit } ) ; 24 | -------------------------------------------------------------------------------- /src/bin/main.rs: -------------------------------------------------------------------------------- 1 | use colored::Colorize; 2 | use std::io::prelude::*; 3 | use structopt::StructOpt; 4 | 5 | use rsc::{ 6 | parse, tokenize, InterpretError, Interpreter, Num, ParseError, ParseErrorCode, TokenizeError, 7 | Variant, 8 | }; 9 | use std::fmt::Display; 10 | use std::ops::Range; 11 | 12 | #[derive(StructOpt)] 13 | #[structopt(about = "A scientific calculator for the terminal.")] 14 | struct Opt { 15 | #[structopt()] 16 | expr: Option, 17 | #[structopt(short = "t", long = "tokens", help = "Prints the tokens")] 18 | tokens: bool, 19 | #[structopt(short = "e", long = "expr", help = "Prints the expression tree")] 20 | bexpr: bool, 21 | #[structopt(short = "v", long = "vars", help = "Prints variable map")] 22 | vars: bool, 23 | #[structopt(long = "no-color", help = "Prevents colored text")] 24 | no_color: bool, 25 | } 26 | 27 | fn main() { 28 | let opt = Opt::from_args(); 29 | 30 | let mut interpreter = Interpreter::default(); 31 | 32 | if let Some(expr) = opt.expr { 33 | match tokenize(&expr) { 34 | Ok(tokens) => match parse(&tokens) { 35 | Ok(expr) => match interpreter.eval(&expr) { 36 | Ok(result) => { 37 | println!("{}", result); 38 | return; 39 | } 40 | Err(e) => eprintln!("{:?}", e), 41 | }, 42 | Err(ParseError { code, span }) => eprintln!("{:?} at {:?}", code, span), 43 | }, 44 | Err(TokenizeError { code, span }) => eprintln!("{:?} at {:?}", code, span), 45 | } 46 | std::process::exit(1); 47 | } 48 | 49 | println!("RSC interactive expression interpreter."); 50 | println!("Try \"help\" for commands and examples."); 51 | 52 | loop { 53 | print!( 54 | "{}", 55 | if opt.no_color { 56 | ">".normal() 57 | } else { 58 | ">".blue() 59 | } 60 | ); 61 | std::io::stdout().flush().unwrap(); 62 | 63 | let mut buffer = String::new(); 64 | std::io::stdin().read_line(&mut buffer).unwrap(); 65 | buffer = buffer.trim().to_owned(); 66 | 67 | if &buffer[..] == "quit" || &buffer[..] == "exit" { 68 | break; 69 | } else if &buffer[..] == "help" { 70 | print_help(opt.no_color); 71 | } else if &buffer[..] == "vars" { 72 | print_vars(&interpreter, opt.no_color); 73 | } else if &buffer[..] == "clear" { 74 | for _ in 0..100 { 75 | println!(); 76 | } 77 | continue; 78 | } else if buffer.starts_with(":") { 79 | continue; 80 | } else { 81 | evaluate( 82 | &buffer, 83 | &mut interpreter, 84 | opt.tokens, 85 | opt.bexpr, 86 | opt.vars, 87 | opt.no_color, 88 | ":", 89 | ); 90 | } 91 | } 92 | } 93 | 94 | const COMMANDS: [(&str, &str); 5] = [ 95 | ("quit|exit", "Close RSC"), 96 | ("help", "Show this help information"), 97 | ("vars", "Display all of the active variables"), 98 | ("clear", "Clear prior output"), 99 | (":", "Write notes"), 100 | ]; 101 | 102 | fn print_help(no_color: bool) { 103 | println!("Commands"); 104 | for (name, desc) in COMMANDS { 105 | println!( 106 | "{:<10} {}", 107 | if no_color { 108 | name.green().clear() 109 | } else { 110 | name.green() 111 | }, 112 | desc 113 | ); 114 | } 115 | println!("\nExamples"); 116 | println!("\t12.3(0.7)"); 117 | println!("\t|-9| + 3!"); 118 | println!("\tx = abs(5)"); 119 | println!("\t-x^4"); 120 | } 121 | 122 | fn get_variant_ord(v: &Variant) -> usize { 123 | match v { 124 | Variant::Num(_) => 1, 125 | Variant::Function(_) => 0, 126 | } 127 | } 128 | 129 | fn print_vars(interpreter: &Interpreter, no_color: bool) { 130 | let mut vars: Vec<(&String, &Variant)> = interpreter.vars.iter().collect(); 131 | vars.sort_by(|(_, v1), (_, v2)| { 132 | // sort by type 133 | let v1_val = get_variant_ord(v1); 134 | let v2_val = get_variant_ord(v2); 135 | v1_val.cmp(&v2_val) 136 | }); 137 | for (id, val) in vars { 138 | let fmt; 139 | match val { 140 | Variant::Num(n) => fmt = format!("{} = {}", &id.green(), n.clone()), 141 | Variant::Function(_) => fmt = format!("{}(..)", &id.green()), 142 | } 143 | println!( 144 | "{}", 145 | if no_color { 146 | fmt.red().clear().to_string() 147 | } else { 148 | fmt 149 | } 150 | ); 151 | } 152 | } 153 | 154 | fn format_error(span: Range, message: &str) -> String { 155 | format!( 156 | " {}{} {}", 157 | " ".repeat(span.start), 158 | "^".repeat(span.len()).red(), 159 | message.red() 160 | ) 161 | } 162 | 163 | fn evaluate( 164 | input: &str, 165 | interpreter: &mut Interpreter, 166 | btokens: bool, 167 | bexpr: bool, 168 | bvars: bool, 169 | bno_color: bool, 170 | success_prefix: &str, 171 | ) { 172 | match tokenize(input) { 173 | Ok(tokens) => { 174 | if btokens { 175 | let fmt = format!("Tokens: {:?}", tokens); 176 | println!( 177 | "{}", 178 | if bno_color { 179 | fmt 180 | } else { 181 | fmt.yellow().to_string() 182 | } 183 | ); 184 | } 185 | match parse(&tokens) { 186 | Ok(expr) => { 187 | if bexpr { 188 | let fmt = format!("Expr: {:#?}", expr); 189 | println!( 190 | "{}", 191 | if bno_color { 192 | fmt 193 | } else { 194 | fmt.yellow().to_string() 195 | } 196 | ); 197 | } 198 | 199 | match interpreter.eval(&expr) { 200 | Ok(result) => { 201 | println!("{}{}", success_prefix, result); 202 | } 203 | Err(err) => { 204 | let fmt = format!("{}", display_interpret_error(&err)); 205 | println!( 206 | "{}", 207 | if bno_color { 208 | fmt 209 | } else { 210 | fmt.red().to_string() 211 | } 212 | ); 213 | } 214 | } 215 | } 216 | Err(ParseError { code, span }) => { 217 | if code == ParseErrorCode::UnexpectedEOF { 218 | println!( 219 | "{}", 220 | format_error(input.len()..input.len() + 1, &format!("{:?}", code)) 221 | ); 222 | } else { 223 | println!("{}", format_error(span, &format!("{:?}", code))); 224 | } 225 | } 226 | } 227 | } 228 | Err(TokenizeError { code, span }) => { 229 | println!("{}", format_error(span, &format!("{:?}", code))); 230 | } 231 | } 232 | if bvars { 233 | for (id, variant) in &interpreter.vars { 234 | let fmt; 235 | if let Variant::Num(n) = variant { 236 | fmt = format!("{} = {}", id, n); 237 | } else { 238 | fmt = format!("{}(..)", id); 239 | } 240 | println!( 241 | "{}", 242 | if bno_color { 243 | fmt 244 | } else { 245 | fmt.yellow().to_string() 246 | } 247 | ); 248 | } 249 | } 250 | } 251 | 252 | #[inline(always)] 253 | fn s_if(b: bool) -> &'static str { 254 | if b { 255 | "s" 256 | } else { 257 | "" 258 | } 259 | } 260 | 261 | fn display_interpret_error(err: &InterpretError) -> String { 262 | match err { 263 | InterpretError::TooFewArgs(id, n) => format!( 264 | "Function {:?} did not receive minimum of {} argument{}.", 265 | id, 266 | n, 267 | s_if(*n != 1) 268 | ), 269 | InterpretError::TooManyArgs(id, n) => format!( 270 | "Function {:?} received more than the maximum {} argument{}.", 271 | id, 272 | n, 273 | s_if(*n != 1) 274 | ), 275 | InterpretError::VarDoesNotExist(id) => format!("No variable or function {:?} exists.", id), 276 | InterpretError::VarIsNotFunction(id) => format!( 277 | "The variable {:?} cannot be used like a function with arguments.", 278 | id 279 | ), 280 | InterpretError::FunctionNameUsedLikeVar(id) => { 281 | format!("The function {:?} cannot be used without arguments.", id) 282 | } 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/expr.rs: -------------------------------------------------------------------------------- 1 | use crate::{Num, OpVal}; 2 | 3 | #[derive(Debug, Clone, PartialEq)] 4 | pub enum Expr<'input, N: Num> { 5 | Eq(Box>, Box>), 6 | FuncOrVarMul(&'input str, Vec>), 7 | Neg(Box>), 8 | Num(&'input N), 9 | Op(OpVal, Box>, Box>), 10 | Var(&'input str), 11 | } 12 | -------------------------------------------------------------------------------- /src/interpreter.rs: -------------------------------------------------------------------------------- 1 | use crate::{Expr, Num, OpVal}; 2 | use std::collections::HashMap; 3 | use std::ops::Deref; 4 | 5 | #[derive(Clone)] 6 | pub enum Variant { 7 | Num(N), 8 | Function(for<'expr> fn(&'expr str, &[N]) -> Result>), 9 | } 10 | 11 | #[derive(Debug, Clone)] 12 | pub enum InterpretError<'expr> { 13 | TooFewArgs(&'expr str, usize), // Id of function, min args 14 | TooManyArgs(&'expr str, usize), // Id of function, max args 15 | VarDoesNotExist(&'expr str), 16 | VarIsNotFunction(&'expr str), 17 | FunctionNameUsedLikeVar(&'expr str), 18 | } 19 | 20 | #[derive(Clone)] 21 | pub struct Interpreter { 22 | pub vars: HashMap>, 23 | } 24 | 25 | impl Interpreter { 26 | #[inline(always)] 27 | pub fn new() -> Interpreter { 28 | Interpreter { 29 | vars: HashMap::new(), 30 | } 31 | } 32 | 33 | #[inline(always)] 34 | pub fn set_var(&mut self, name: String, value: Variant) { 35 | self.vars.insert(name, value); 36 | } 37 | 38 | #[inline(always)] 39 | pub fn delete_var(&mut self, name: &str) -> Option> { 40 | self.vars.remove(name) 41 | } 42 | 43 | pub fn eval<'expr>(&mut self, expr: &'expr Expr) -> Result> { 44 | // simple, naive recursive tree walk 45 | match expr { 46 | Expr::Eq(lhs, rhs) => match lhs.deref() { 47 | Expr::Var(id) => { 48 | let result = self.eval(rhs)?; 49 | if let Some(val) = self.vars.get_mut(*id) { 50 | *val = Variant::Num(result.clone()); 51 | } else { 52 | self.vars 53 | .insert(id.to_string(), Variant::Num(result.clone())); 54 | } 55 | Ok(result) 56 | } 57 | _ => todo!("implement algebra solving"), 58 | }, 59 | Expr::FuncOrVarMul(id, exprs) => { 60 | let mut args = Vec::with_capacity(exprs.len()); 61 | for expr in exprs { 62 | args.push(self.eval(expr)?); 63 | } 64 | 65 | if let Some(var) = self.vars.get(*id) { 66 | match var { 67 | Variant::Num(n) => { 68 | if args.len() == 1 { 69 | let arg = args.remove(0); 70 | Ok(n.clone().mul(arg)) 71 | } else { 72 | Err(InterpretError::VarIsNotFunction(id)) 73 | } 74 | } 75 | Variant::Function(func) => func(id, &args), 76 | } 77 | } else { 78 | Err(InterpretError::VarDoesNotExist(id)) 79 | } 80 | } 81 | Expr::Neg(expr) => Ok(-self.eval(expr)?), 82 | Expr::Num(n) => Ok((**n).clone()), 83 | Expr::Op(op, lhs, rhs) => { 84 | let lhs = self.eval(lhs)?; 85 | let rhs = self.eval(rhs)?; 86 | Ok(match op { 87 | OpVal::Add => lhs + rhs, 88 | OpVal::Sub => lhs - rhs, 89 | OpVal::Mul => lhs * rhs, 90 | OpVal::Div => lhs / rhs, 91 | OpVal::Mod => lhs % rhs, 92 | OpVal::Pow => lhs.pow(rhs), 93 | _ => unreachable!(), 94 | }) 95 | } 96 | Expr::Var(id) => { 97 | if let Some(var) = self.vars.get(*id) { 98 | match var { 99 | Variant::Num(n) => Ok(n.clone()), 100 | Variant::Function(_) => Err(InterpretError::FunctionNameUsedLikeVar(id)), 101 | } 102 | } else { 103 | Err(InterpretError::VarDoesNotExist(id)) 104 | } 105 | } 106 | } 107 | } 108 | } 109 | 110 | #[inline] 111 | pub fn ensure_arg_count( 112 | min: usize, 113 | max: usize, 114 | args_len: usize, 115 | func_id: &str, 116 | ) -> Result<(), InterpretError> { 117 | if args_len < min { 118 | Err(InterpretError::TooFewArgs(func_id, min)) 119 | } else if args_len > max { 120 | Err(InterpretError::TooManyArgs(func_id, max)) 121 | } else { 122 | Ok(()) 123 | } 124 | } 125 | 126 | impl Default for Interpreter { 127 | fn default() -> Self { 128 | let mut vars = HashMap::new(); 129 | vars.insert(String::from("pi"), Variant::Num(std::f64::consts::PI)); 130 | vars.insert(String::from("e"), Variant::Num(std::f64::consts::E)); 131 | vars.insert(String::from("tau"), Variant::Num(std::f64::consts::TAU)); 132 | vars.insert( 133 | String::from("abs"), 134 | Variant::Function(|id, args| { 135 | ensure_arg_count(1, 1, args.len(), id)?; 136 | Ok(args[0].abs()) 137 | }), 138 | ); 139 | vars.insert( 140 | String::from("sqrt"), 141 | Variant::Function(|id, args| { 142 | ensure_arg_count(1, 1, args.len(), id)?; 143 | Ok(args[0].sqrt()) 144 | }), 145 | ); 146 | vars.insert( 147 | String::from("factorial"), 148 | Variant::Function(|id, args| { 149 | ensure_arg_count(1, 1, args.len(), id)?; 150 | let n = args[0]; 151 | if n <= 1.0 { 152 | Ok(1.0) 153 | } else { 154 | Ok((1..=n as u64).product::() as f64) 155 | } 156 | }), 157 | ); 158 | Interpreter { vars } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod expr; 2 | mod interpreter; 3 | mod parser; 4 | mod tokenizer; 5 | 6 | pub use expr::*; 7 | pub use interpreter::*; 8 | pub use parser::*; 9 | pub use tokenizer::*; 10 | 11 | use std::fmt::Debug; 12 | use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, Sub, SubAssign}; 13 | use std::str::FromStr; 14 | 15 | /// Defines the minimum operations and definitions to parse and evaluate expressions. 16 | pub trait Num: 17 | Debug 18 | + Clone 19 | + PartialEq 20 | + PartialOrd 21 | + FromStr 22 | + Add 23 | + Sub 24 | + Mul 25 | + Div 26 | + Rem 27 | + Neg 28 | + AddAssign 29 | + SubAssign 30 | + MulAssign 31 | + DivAssign 32 | { 33 | /// Returns the additive identity value, 0, for the number. 34 | fn zero() -> Self; 35 | /// Returns the multiplicative identity value, 1, for the number. 36 | fn one() -> Self; 37 | /// Returns true if the number is a whole integer without a fractional part. E.g. 1 or 3. 38 | fn is_whole(&self) -> bool; 39 | /// Returns number to the power of `other`. 40 | fn pow(self, other: Self) -> Self; 41 | } 42 | 43 | macro_rules! impl_num_for_integer { 44 | ($itype:ty) => { 45 | impl Num for $itype { 46 | #[inline(always)] 47 | fn zero() -> Self { 48 | 0 49 | } 50 | #[inline(always)] 51 | fn one() -> Self { 52 | 1 53 | } 54 | #[inline(always)] 55 | fn is_whole(&self) -> bool { 56 | true 57 | } 58 | #[inline(always)] 59 | fn pow(self, other: Self) -> Self { 60 | self.wrapping_pow(other as u32) // Wraps on overflow... 61 | } 62 | } 63 | }; 64 | } 65 | impl_num_for_integer!(i8); 66 | impl_num_for_integer!(i16); 67 | impl_num_for_integer!(i32); 68 | impl_num_for_integer!(i64); 69 | impl_num_for_integer!(i128); 70 | impl_num_for_integer!(isize); 71 | 72 | macro_rules! impl_num_for_float { 73 | ($ftype:ty) => { 74 | impl Num for $ftype { 75 | #[inline(always)] 76 | fn zero() -> Self { 77 | 0.0 78 | } 79 | #[inline(always)] 80 | fn one() -> Self { 81 | 1.0 82 | } 83 | #[inline(always)] 84 | fn is_whole(&self) -> bool { 85 | self.fract() == 0.0 86 | } 87 | #[inline(always)] 88 | fn pow(self, other: Self) -> Self { 89 | self.powf(other) // inf or -inf if overflowed... 90 | } 91 | } 92 | }; 93 | } 94 | impl_num_for_float!(f32); 95 | impl_num_for_float!(f64); 96 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | use crate::{Expr, Num, OpVal, SymbolVal, Token, TokenValue}; 2 | use peekmore::{PeekMore, PeekMoreIterator}; 3 | use std::ops::Range; 4 | use std::slice::Iter; 5 | 6 | #[derive(Debug, Copy, Clone, PartialEq)] 7 | pub enum ParseErrorCode<'t, N: Num> { 8 | ExpectedValue, 9 | ExpectedClosingParen, 10 | UnexpectedToken(&'t Token<'t, N>), 11 | UnexpectedEOF, 12 | } 13 | use ParseErrorCode::*; 14 | 15 | #[derive(Debug, Clone)] 16 | pub struct ParseError<'t, N: Num> { 17 | pub code: ParseErrorCode<'t, N>, 18 | pub span: Range, 19 | } 20 | 21 | pub type ParseResult<'input, N> = Result, ParseError<'input, N>>; 22 | 23 | macro_rules! error { 24 | ($code:expr, $span:expr) => { 25 | ParseError { 26 | code: $code, 27 | span: $span, 28 | } 29 | }; 30 | } 31 | 32 | type TokenIter<'t, N> = PeekMoreIterator>>; 33 | 34 | pub fn parse<'input, N: Num>(tokens: &'input [Token<'input, N>]) -> ParseResult<'input, N> { 35 | let mut iter = tokens.iter().peekmore(); 36 | let result = parse_expr(&mut iter); 37 | match result { 38 | Ok(_) => { 39 | if let Some(tok) = iter.next() { 40 | Err(error!(UnexpectedToken(tok), tok.span.clone())) 41 | } else { 42 | result 43 | } 44 | } 45 | Err(_) => result, 46 | } 47 | } 48 | 49 | #[inline] 50 | fn parse_expr<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> ParseResult<'t, N> { 51 | parse_eq(tokens) 52 | } 53 | 54 | fn parse_eq<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> ParseResult<'t, N> { 55 | let mut result = parse_add(tokens)?; 56 | while let Some(peek_tok) = tokens.peek() { 57 | if peek_tok.value == TokenValue::Op(OpVal::Eq) { 58 | tokens.next(); // Consume '=' 59 | let rhs = parse_add(tokens)?; 60 | result = Expr::Eq(Box::new(result), Box::new(rhs)); 61 | } else { 62 | break; 63 | } 64 | } 65 | Ok(result) 66 | } 67 | 68 | fn parse_add<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> ParseResult<'t, N> { 69 | let mut result = parse_mul(tokens)?; 70 | while let Some(peek_tok) = tokens.peek() { 71 | match peek_tok.value { 72 | TokenValue::Op(op) if op == OpVal::Add || op == OpVal::Sub => { 73 | tokens.next(); // Consume '+' or '-' 74 | let rhs = parse_mul(tokens)?; 75 | result = Expr::Op(op, Box::new(result), Box::new(rhs)); 76 | } 77 | _ => break, 78 | } 79 | } 80 | Ok(result) 81 | } 82 | 83 | fn parse_mul<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> ParseResult<'t, N> { 84 | let mut result = parse_pow(tokens)?; 85 | while let Some(peek_tok) = tokens.peek() { 86 | match peek_tok.value { 87 | TokenValue::Op(op) if op == OpVal::Mul || op == OpVal::Div || op == OpVal::Mod => { 88 | tokens.next(); // Consume '*' or '/' or '%' 89 | let rhs = parse_pow(tokens)?; 90 | result = Expr::Op(op, Box::new(result), Box::new(rhs)); 91 | } 92 | _ => break, 93 | } 94 | } 95 | Ok(result) 96 | } 97 | 98 | fn parse_pow<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> ParseResult<'t, N> { 99 | let mut result = parse_parentheses_mul(tokens)?; 100 | while let Some(peek_tok) = tokens.peek() { 101 | if peek_tok.value == TokenValue::Op(OpVal::Pow) { 102 | tokens.next(); // Consume '^' 103 | let rhs = parse_factor(tokens)?; 104 | result = Expr::Op(OpVal::Pow, Box::new(result), Box::new(rhs)); 105 | } else { 106 | break; 107 | } 108 | } 109 | Ok(result) 110 | } 111 | 112 | fn parse_parentheses_mul<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> ParseResult<'t, N> { 113 | if let Some(func_or_var_mul) = parse_func_or_var_mul(tokens) { 114 | Ok(func_or_var_mul?) 115 | } else { 116 | let mut result = parse_factorial(tokens)?; 117 | while let Some(peek_tok) = tokens.peek() { 118 | if peek_tok.value == TokenValue::Symbol(SymbolVal::LP) { 119 | tokens.next(); // Consume '(' 120 | let rhs = parse_expr(tokens)?; 121 | if let Some(tok) = tokens.next() { 122 | if tok.value == TokenValue::Symbol(SymbolVal::RP) { 123 | result = Expr::Op(OpVal::Mul, Box::new(result), Box::new(rhs)); 124 | } else { 125 | return Err(error!(ExpectedClosingParen, tok.span.clone())); 126 | } 127 | } else { 128 | return Err(error!(UnexpectedEOF, 0..0)); 129 | } 130 | } else { 131 | break; 132 | } 133 | } 134 | Ok(result) 135 | } 136 | } 137 | 138 | // This function returns Option to the result, because it doesn't *have* to parse a value. 139 | // And because it should only be used by parse_parentheses_mul. 140 | fn parse_func_or_var_mul<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> Option> { 141 | match tokens.peek() { 142 | Some(Token { 143 | value: TokenValue::Id(id), 144 | .. 145 | }) => { 146 | // Check for opening parentheses 147 | if let Some(tok) = tokens.peek_nth(1) { 148 | if tok.value != TokenValue::Symbol(SymbolVal::LP) { 149 | return None; 150 | } 151 | } else { 152 | return None; 153 | } 154 | 155 | // Consume previous tokens 156 | tokens.next(); // id 157 | tokens.next(); // '(' 158 | 159 | // At this point, the function should always return a Some(...) 160 | 161 | // Shortcut: function has no parameters 162 | if let Some(tok) = tokens.peek() { 163 | if tok.value == TokenValue::Symbol(SymbolVal::RP) { 164 | tokens.next(); // Consume ')' 165 | return Some(Ok(Expr::FuncOrVarMul(id, Vec::new()))); 166 | } 167 | } 168 | 169 | // Collecting function parameters 170 | let mut params = Vec::with_capacity(3); 171 | while let Ok(expr) = parse_expr(tokens) { 172 | params.push(expr); 173 | match tokens.next() { 174 | Some(Token { 175 | value: TokenValue::Symbol(SymbolVal::Comma), 176 | .. 177 | }) => { 178 | continue; 179 | } 180 | Some(Token { 181 | value: TokenValue::Symbol(SymbolVal::RP), 182 | .. 183 | }) => { 184 | break; 185 | } 186 | Some(tok) => return Some(Err(error!(UnexpectedToken(tok), tok.span.clone()))), 187 | None => return Some(Err(error!(UnexpectedEOF, 0..0))), 188 | } 189 | } 190 | Some(Ok(Expr::FuncOrVarMul(id, params))) 191 | } 192 | _ => None, 193 | } 194 | } 195 | 196 | fn parse_factorial<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> ParseResult<'t, N> { 197 | let mut result = parse_factor(tokens)?; 198 | while let Some(peek_tok) = tokens.peek() { 199 | if peek_tok.value == TokenValue::Op(OpVal::Exclaim) { 200 | tokens.next(); // Consume '!' 201 | result = Expr::FuncOrVarMul("factorial", vec![result]); 202 | } else { 203 | break; 204 | } 205 | } 206 | Ok(result) 207 | } 208 | 209 | fn parse_factor<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> ParseResult<'t, N> { 210 | match tokens.next() { 211 | Some(tok) => match &tok.value { 212 | TokenValue::Num(num) => Ok(Expr::Num(num)), 213 | TokenValue::Id(id) => Ok(Expr::Var(id)), 214 | TokenValue::Op(op) => match op { 215 | OpVal::Sub => Ok(Expr::Neg(Box::new(parse_expr(tokens)?))), 216 | _ => Err(error!(UnexpectedToken(tok), tok.span.clone())), 217 | }, 218 | TokenValue::Symbol(sym) => match sym { 219 | SymbolVal::LP => { 220 | let expr = parse_expr(tokens)?; 221 | // Expect a closing parentheses 222 | if let Some(tok) = tokens.next() { 223 | if tok.value == TokenValue::Symbol(SymbolVal::RP) { 224 | Ok(expr) 225 | } else { 226 | Err(error!(UnexpectedToken(tok), tok.span.clone())) 227 | } 228 | } else { 229 | Err(error!(UnexpectedEOF, 0..0)) 230 | } 231 | } 232 | SymbolVal::Pipe => { 233 | let expr = parse_expr(tokens)?; 234 | // Expect a closing pipe 235 | if let Some(tok) = tokens.next() { 236 | if tok.value == TokenValue::Symbol(SymbolVal::Pipe) { 237 | Ok(Expr::FuncOrVarMul("abs", vec![expr])) 238 | } else { 239 | Err(error!(UnexpectedToken(tok), tok.span.clone())) 240 | } 241 | } else { 242 | Err(error!(UnexpectedEOF, 0..0)) 243 | } 244 | } 245 | _ => Err(error!(UnexpectedToken(tok), tok.span.clone())), 246 | }, 247 | }, 248 | None => Err(error!(UnexpectedEOF, 0..0)), 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/tokenizer.rs: -------------------------------------------------------------------------------- 1 | use crate::Num; 2 | use std::ops::Range; 3 | 4 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 5 | pub enum OpVal { 6 | Add, 7 | Sub, 8 | Mul, 9 | Div, 10 | Mod, 11 | Pow, 12 | Eq, 13 | Exclaim, 14 | } 15 | use OpVal::*; 16 | 17 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 18 | pub enum SymbolVal { 19 | LP, 20 | RP, 21 | Comma, 22 | Pipe, 23 | } 24 | use SymbolVal::*; 25 | 26 | #[derive(Debug, Clone, PartialEq)] 27 | pub enum TokenValue<'input, N: Num> { 28 | Num(N), 29 | Id(&'input str), 30 | Op(OpVal), 31 | Symbol(SymbolVal), 32 | } 33 | use TokenValue::*; 34 | 35 | #[derive(Debug, Clone, PartialEq)] 36 | pub struct Token<'input, N: Num> { 37 | pub value: TokenValue<'input, N>, 38 | pub span: Range, 39 | } 40 | 41 | #[derive(Debug, Copy, Clone, PartialEq)] 42 | pub enum TokenizeErrorCode<'input> { 43 | InvalidNumber(&'input str), 44 | UnrecognizedChar(char), 45 | } 46 | use TokenizeErrorCode::*; 47 | 48 | #[derive(Debug, Clone, PartialEq)] 49 | pub struct TokenizeError<'input> { 50 | pub code: TokenizeErrorCode<'input>, 51 | pub span: Range, 52 | } 53 | 54 | #[derive(Debug, Clone, Default)] 55 | pub struct TokenizeOptions { 56 | identifiers_contain_numbers: bool, 57 | } 58 | 59 | pub fn tokenize(input: &str) -> Result>, TokenizeError> { 60 | tokenize_with_options(input, TokenizeOptions::default()) 61 | } 62 | 63 | pub fn tokenize_with_options( 64 | input: &str, 65 | options: TokenizeOptions, 66 | ) -> Result>, TokenizeError> { 67 | let mut tokens = Vec::with_capacity(16); 68 | let mut chars = input.chars().enumerate().peekable(); 69 | 70 | macro_rules! push_token { 71 | ($token:expr, $pos:expr, $len:expr) => { 72 | tokens.push(Token { 73 | value: $token, 74 | span: Range { 75 | start: $pos, 76 | end: $pos + $len, 77 | }, 78 | }) 79 | }; 80 | } 81 | 82 | while let Some((cpos, c)) = chars.next() { 83 | match c { 84 | '+' => push_token!(Op(Add), cpos, 1), 85 | '-' => push_token!(Op(Sub), cpos, 1), 86 | '*' => push_token!(Op(Mul), cpos, 1), 87 | '/' => push_token!(Op(Div), cpos, 1), 88 | '%' => push_token!(Op(Mod), cpos, 1), 89 | '^' => push_token!(Op(Pow), cpos, 1), 90 | '=' => push_token!(Op(Eq), cpos, 1), 91 | '!' => push_token!(Op(Exclaim), cpos, 1), 92 | 93 | '(' => push_token!(Symbol(LP), cpos, 1), 94 | ')' => push_token!(Symbol(RP), cpos, 1), 95 | ',' => push_token!(Symbol(Comma), cpos, 1), 96 | '|' => push_token!(Symbol(Pipe), cpos, 1), 97 | 98 | _ => { 99 | if c.is_ascii_digit() || c == '.' { 100 | let start = cpos; 101 | let mut end = start + 1; 102 | while let Some((_, nc)) = chars.peek() { 103 | if nc.is_ascii_digit() || *nc == '.' { 104 | chars.next(); // Consume nc 105 | end += 1; 106 | } else { 107 | break; 108 | } 109 | } 110 | if let Ok(num) = input[start..end].parse::() { 111 | push_token!(Num(num), start, end - start); 112 | } else { 113 | return Err(TokenizeError { 114 | code: InvalidNumber(&input[start..end]), 115 | span: start..end, 116 | }); 117 | } 118 | } else if c == '_' || c.is_alphabetic() { 119 | let start = cpos; 120 | let mut end = start + 1; 121 | while let Some((_, nc)) = chars.peek() { 122 | // If it is any of _ A-z (or digits if option) 123 | if *nc == '_' 124 | || nc.is_alphanumeric() 125 | || (options.identifiers_contain_numbers && nc.is_ascii_digit()) 126 | { 127 | chars.next(); // Consume next character 128 | end += 1; 129 | } else { 130 | break; 131 | } 132 | } 133 | push_token!(Id(&input[start..end]), start, end - start); 134 | } else if !c.is_whitespace() { 135 | return Err(TokenizeError { 136 | code: UnrecognizedChar(c), 137 | span: cpos..cpos + 1, 138 | }); 139 | } 140 | } 141 | } 142 | } 143 | Ok(tokens) 144 | } 145 | --------------------------------------------------------------------------------