├── .github └── workflows │ └── rust.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── tinyexpr.rs ├── src ├── error.rs └── lib.rs └── tests └── lib.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | name: Build and Test 15 | strategy: 16 | matrix: 17 | os: [Ubuntu-latest, Windows-latest, MacOS-latest] 18 | runs-on: ${{ matrix.os }} 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: stable 25 | override: true 26 | - name: cargo fetch 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: fetch 30 | - name: cargo test 31 | uses: actions-rs/cargo@v1 32 | with: 33 | command: test 34 | args: --verbose 35 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tinyexpr" 3 | version = "0.1.2" 4 | description = "Tiny recursive descent expression parser, compiler, and evaluation engine for math expressions. A work in progress port of TinyExpr library to Rust." 5 | keywords = ["tinyexpr", "math", "expression", "parser"] 6 | license = "MIT/Apache-2.0" 7 | authors = ["Krzysztof Kondrak "] 8 | repository = "https://github.com/kondrak/tinyexpr-rs" 9 | homepage = "https://github.com/kondrak/tinyexpr-rs" 10 | documentation = "https://docs.rs/tinyexpr" 11 | 12 | [dependencies] 13 | bitflags = "1.3.2" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Krzysztof Kondrak 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 | # tinyexpr-rs 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/tinyexpr.svg)](https://crates.io/crates/tinyexpr) 4 | [![Documentation](https://docs.rs/tinyexpr/badge.svg)](https://docs.rs/tinyexpr) 5 | [![CI](https://github.com/kondrak/tinyexpr-rs/actions/workflows/rust.yml/badge.svg)](https://github.com/kondrak/tinyexpr-rs/actions/workflows/rust.yml) 6 | [![Coverage Status](https://coveralls.io/repos/github/kondrak/tinyexpr-rs/badge.svg?branch=master)](https://coveralls.io/github/kondrak/tinyexpr-rs?branch=master) 7 | ![](https://img.shields.io/crates/l/json.svg) 8 | 9 | Tiny recursive descent expression parser, compiler, and evaluation engine for math expressions. 10 | 11 | This is a WIP port of [TinyExpr](https://github.com/codeplea/tinyexpr) to Rust. Current release only supports built-in system functions (trigonometry, algebraic operations, constants, etc.). See the `tests` module for more examples. 12 | 13 | [Documentation](https://docs.rs/tinyexpr) 14 | 15 | Usage 16 | ----- 17 | ```toml 18 | # Cargo.toml 19 | [dependencies] 20 | tinyexpr = "0.1" 21 | ``` 22 | 23 | Example 24 | ------- 25 | ```rust 26 | extern crate tinyexpr; 27 | 28 | fn main() 29 | { 30 | // parse the expression and fetch result 31 | let r = tinyexpr::interp("2+2*2").unwrap(); 32 | 33 | // should print "6" 34 | println!("{:?}", r); 35 | } 36 | ``` 37 | 38 | Build instructions 39 | ------------------ 40 | 41 | ``` 42 | cargo build 43 | cargo run --example tinyexpr 44 | ``` 45 | 46 | ## Todo 47 | - support custom user functions 48 | - support functions taking more than 2 parameters 49 | - support closures 50 | 51 | ## License 52 | 53 | Licensed under either of 54 | 55 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 56 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 57 | -------------------------------------------------------------------------------- /examples/tinyexpr.rs: -------------------------------------------------------------------------------- 1 | extern crate tinyexpr; 2 | 3 | fn main() 4 | { 5 | // evaluate expression and fetch result 6 | let result = tinyexpr::interp("2*1/sin(pi/2)").unwrap_or_else(|e| { 7 | panic!("{}", e); 8 | }); 9 | 10 | println!("{:?}", result); 11 | } 12 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error type for tinyexpr crate. 2 | 3 | use std::error::Error; 4 | use std::fmt; 5 | use std::result; 6 | use std::num::ParseFloatError; 7 | 8 | /// Result type used throughout the crate. 9 | pub type Result = result::Result; 10 | 11 | /// Error type for tinyexpr-rs crate. 12 | #[derive(Debug)] 13 | pub enum TinyExprError { 14 | /// Parse error 15 | Parse(ParseFloatError), 16 | /// Any other kind of error 17 | Other(String) 18 | } 19 | 20 | impl fmt::Display for TinyExprError { 21 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 22 | match *self { 23 | TinyExprError::Parse(ref err) => err.fmt(f), 24 | TinyExprError::Other(ref err) => err.fmt(f) 25 | } 26 | } 27 | } 28 | 29 | impl Error for TinyExprError { 30 | fn description(&self) -> &str { 31 | match *self { 32 | TinyExprError::Parse(ref err) => err.description(), 33 | TinyExprError::Other(ref err) => err 34 | } 35 | } 36 | } 37 | 38 | impl From for TinyExprError { 39 | fn from(err: String) -> TinyExprError { 40 | TinyExprError::Other(err) 41 | } 42 | } 43 | 44 | impl From for TinyExprError { 45 | fn from(err: ParseFloatError) -> TinyExprError { 46 | TinyExprError::Parse(err) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [TinyExpr](https://github.com/kondrak/tinyexpr-rs) is a tiny recursive descent expression 2 | //! parser, compiler, and evaluation engine for math expressions. 3 | //! This is a work in progress port of [TinyExpr](https://github.com/codeplea/tinyexpr) to Rust. 4 | //! 5 | //! Current release only supports built-in system functions (trigonometry, algebraic operations, constants, etc.). 6 | //! See the `tests` module for more examples. 7 | //! 8 | //!# Quick Start 9 | //! 10 | //!``` 11 | //!extern crate tinyexpr; 12 | //! 13 | //!fn main() 14 | //!{ 15 | //! // parse the expression and fetch result 16 | //! let r = tinyexpr::interp("2+2*2").unwrap(); 17 | //! 18 | //! // should print "6" 19 | //! println!("{:?}", r); 20 | //!} 21 | //!``` 22 | #[macro_use] 23 | extern crate bitflags; 24 | pub mod error; 25 | use error::Result; 26 | use std::f64::consts; 27 | use std::str::FromStr; 28 | 29 | bitflags! { 30 | #[doc(hidden)] 31 | pub struct ExprType: u64 { 32 | const TE_VARIABLE = 0; 33 | const TE_CONSTANT = 1; 34 | const TE_FUNCTION0 = 8; 35 | const TE_FUNCTION1 = 9; 36 | const TE_FUNCTION2 = 10; 37 | const TE_FUNCTION3 = 11; 38 | const TE_FUNCTION4 = 12; 39 | const TE_FUNCTION5 = 13; 40 | const TE_FUNCTION6 = 14; 41 | const TE_FUNCTION7 = 15; 42 | const TE_CLOSURE0 = 16; 43 | const TE_CLOSURE1 = 17; 44 | const TE_CLOSURE2 = 18; 45 | const TE_CLOSURE3 = 19; 46 | const TE_CLOSURE4 = 20; 47 | const TE_CLOSURE5 = 21; 48 | const TE_CLOSURE6 = 22; 49 | const TE_CLOSURE7 = 23; 50 | const TE_FLAG_PURE = 32; 51 | const TOK_NULL = 24; 52 | const TOK_ERROR = 25; 53 | const TOK_END = 26; 54 | const TOK_SEP = 27; 55 | const TOK_OPEN = 28; 56 | const TOK_CLOSE = 29; 57 | const TOK_NUMBER = 30; 58 | const TOK_VARIABLE = 31; 59 | const TOK_INFIX = 32; 60 | const T_MASK = 0x0000001F; 61 | } 62 | } 63 | 64 | #[allow(unused_macros)] 65 | macro_rules! type_mask { 66 | ($x:expr) => ($x & ExprType::T_MASK) 67 | } 68 | 69 | #[allow(unused_macros)] 70 | macro_rules! is_pure { 71 | ($x:expr) => (($x & ExprType::TE_FLAG_PURE).bits() != 0) 72 | } 73 | 74 | #[allow(unused_macros)] 75 | macro_rules! is_function { 76 | ($x:expr) => (($x & ExprType::TE_FUNCTION0).bits() != 0) 77 | } 78 | 79 | #[allow(unused_macros)] 80 | macro_rules! is_closure { 81 | ($x:expr) => (($x & ExprType::TE_CLOSURE0).bits() != 0) 82 | } 83 | 84 | #[allow(unused_macros)] 85 | macro_rules! arity { 86 | ($x:expr) => (if($x & (ExprType::TE_FUNCTION0 | ExprType::TE_CLOSURE0)).bits() != 0 { $x.bits() & 0x00000007 } else { 0 }) 87 | } 88 | 89 | // todo: introduce a Function struct to accomodate different arg numbers and ret values? 90 | const FUNCTIONS: [&'static str; 21] = ["abs", "acos", "asin", "atan", "atan2", "ceil", "cos", 91 | "cosh", "e", "exp", "floor", "ln", "log", "log10", 92 | "pi", "pow", "sin", "sinh", "sqrt", "tan", "tanh" ]; 93 | const FUNCTION_TYPES: [(fn(f64, f64) -> f64, ExprType); 21] = [ (abs, ExprType::TE_FUNCTION1), (acos, ExprType::TE_FUNCTION1), (asin, ExprType::TE_FUNCTION1), 94 | (atan, ExprType::TE_FUNCTION1), (atan2, ExprType::TE_FUNCTION2), (ceil, ExprType::TE_FUNCTION1), 95 | (cos, ExprType::TE_FUNCTION1), (cosh, ExprType::TE_FUNCTION1), (e, ExprType::TE_FUNCTION0), 96 | (exp, ExprType::TE_FUNCTION1), (floor, ExprType::TE_FUNCTION1), (ln, ExprType::TE_FUNCTION1), 97 | (log, ExprType::TE_FUNCTION1), (log10, ExprType::TE_FUNCTION1), (pi, ExprType::TE_FUNCTION0), 98 | (pow, ExprType::TE_FUNCTION2), (sin, ExprType::TE_FUNCTION1), (sinh, ExprType::TE_FUNCTION1), 99 | (sqrt, ExprType::TE_FUNCTION1), (tan, ExprType::TE_FUNCTION1), (tanh, ExprType::TE_FUNCTION1)]; 100 | 101 | fn dummy(_: f64, _: f64) -> f64 { panic!("called dummy!") } // todo 102 | fn add(a: f64, b: f64) -> f64 { a + b } 103 | fn sub(a: f64, b: f64) -> f64 { a - b } 104 | fn mul(a: f64, b: f64) -> f64 { a * b } 105 | fn div(a: f64, b: f64) -> f64 { a / b } 106 | fn fmod(a: f64, b: f64) -> f64 { a % b } 107 | fn neg(a: f64, _: f64) -> f64 { -a } 108 | fn comma(_: f64, b: f64) -> f64 { b } 109 | // todo: this is added so that it works with current fptr... - need more types! no extra unused params! 110 | fn abs(a: f64, _: f64) -> f64 { a.abs() } 111 | fn acos(a: f64, _: f64) -> f64 { a.acos() } 112 | fn asin(a: f64, _: f64) -> f64 { a.asin() } 113 | fn atan(a: f64, _: f64) -> f64 { a.atan() } 114 | fn atan2(a: f64, b: f64) -> f64 { a.atan2(b) } 115 | fn ceil(a: f64, _: f64) -> f64 { a.ceil() } 116 | fn cos(a: f64, _: f64) -> f64 { a.cos() } 117 | fn cosh(a: f64, _: f64) -> f64 { a.cosh() } 118 | fn e(_: f64, _: f64) -> f64 { consts::E } 119 | fn exp(a: f64, _: f64) -> f64 { a.exp() } 120 | fn floor(a: f64, _: f64) -> f64 { a.floor() } 121 | fn ln(a: f64, _: f64) -> f64 { a.ln() } 122 | fn log(a: f64, _: f64) -> f64 { a.log10() } // todo ? 123 | fn log10(a: f64, _: f64) -> f64 { a.log10() } 124 | fn pi(_: f64, _: f64) -> f64 { consts::PI } 125 | fn pow(a: f64, b: f64) -> f64 { a.powf(b) } 126 | fn sin(a: f64, _: f64) -> f64 { a.sin() } 127 | fn sinh(a: f64, _: f64) -> f64 { a.sinh() } 128 | fn sqrt(a: f64, _: f64) -> f64 { a.sqrt() } 129 | fn tan(a: f64, _: f64) -> f64 { a.tan() } 130 | fn tanh(a: f64, _: f64) -> f64 { a.tanh() } 131 | 132 | #[doc(hidden)] 133 | #[derive(Debug)] 134 | pub struct Expr { 135 | pub e_type: ExprType, 136 | pub value: f64, 137 | pub bound: i8, // todo: Variable? 138 | pub function: fn(f64, f64) -> f64, 139 | pub parameters: Vec // todo: should this be Option<>? Also, Expr&? 140 | } 141 | 142 | impl Expr { 143 | fn new() -> Expr { 144 | Expr { 145 | e_type: ExprType::TOK_NULL, 146 | value: 0.0, 147 | bound: 0, 148 | function: dummy, 149 | parameters: Vec::::new() 150 | } 151 | } 152 | } 153 | 154 | impl Clone for Expr { 155 | fn clone(&self) -> Expr { 156 | Expr { 157 | e_type: self.e_type, 158 | value: self.value, 159 | bound: self.bound, 160 | function: self.function, 161 | parameters: self.parameters.clone() 162 | } 163 | } 164 | } 165 | 166 | 167 | #[doc(hidden)] 168 | #[derive(Debug)] 169 | pub struct Variable { 170 | pub name: String, 171 | pub address: i8, // todo: this will have to go - handle variables? (no void*) 172 | pub function: fn(f64, f64) -> f64, 173 | pub v_type: ExprType, 174 | pub context: Vec, 175 | } 176 | 177 | impl Variable { 178 | fn new(name: &str, v_type: ExprType) -> Variable { 179 | Variable { 180 | name: String::from(name), 181 | address: 0, 182 | function: dummy, 183 | v_type: v_type, 184 | context: Vec::::new(), 185 | } 186 | } 187 | } 188 | 189 | impl Clone for Variable { 190 | fn clone(&self) -> Variable { 191 | Variable { 192 | name: self.name.clone(), 193 | address: self.address, 194 | function: self.function, 195 | v_type: self.v_type, 196 | context: self.context.clone() 197 | } 198 | } 199 | } 200 | 201 | #[derive(Debug)] 202 | struct State { 203 | pub next: String, 204 | pub s_type: ExprType, 205 | pub n_idx: usize, 206 | pub value: f64, 207 | pub bound: i8, 208 | pub function: fn(f64, f64) -> f64, 209 | pub context: Vec, 210 | pub lookup: Vec, 211 | } 212 | 213 | impl State { 214 | fn new(expression: &str) -> State { 215 | State { 216 | next: String::from(expression), 217 | s_type: ExprType::TOK_NULL, 218 | n_idx: 0, 219 | value: 0.0, 220 | bound: 0, 221 | function: mul, 222 | context: Vec::::new(), 223 | lookup: Vec::::new() 224 | } 225 | } 226 | } 227 | 228 | // todo 229 | fn new_expr(e_type: ExprType, params: Option>) -> Expr { 230 | let _arity = arity!(e_type); 231 | let mut ret = Expr::new(); 232 | // just create a new expression with new type based on old expression, no weird memcpy mumbo jumbo 233 | ret.e_type = e_type; 234 | ret.bound = 0; 235 | if let Some(params) = params { 236 | ret.parameters = params; 237 | } 238 | 239 | ret 240 | } 241 | 242 | fn find_lookup(s: &State, txt: &str) -> Option { 243 | for var in &s.lookup { 244 | if &(*var.name) == txt { 245 | return Some((*var).clone()); 246 | } 247 | } 248 | 249 | None 250 | } 251 | 252 | fn find_builtin(txt: &str) -> Option { 253 | if let Ok(idx) = FUNCTIONS.binary_search(&txt) { 254 | let mut v = Variable::new(txt, FUNCTION_TYPES[idx].1 | ExprType::TE_FLAG_PURE); 255 | v.function = FUNCTION_TYPES[idx].0; 256 | return Some(v); 257 | } 258 | 259 | None 260 | } 261 | 262 | fn next_token(s: &mut State) -> Result { 263 | s.s_type = ExprType::TOK_NULL; 264 | 265 | while s.s_type == ExprType::TOK_NULL { 266 | if s.n_idx == s.next.len() { 267 | s.s_type = ExprType::TOK_END; 268 | break; 269 | } 270 | 271 | let next_char = s.next.as_bytes()[s.n_idx] as char; 272 | // try reading a number 273 | if (next_char >= '0' && next_char <= '9') || next_char == '.' { 274 | let mut num_str = String::new(); 275 | let mut c = next_char; 276 | 277 | // extract the number part to separate string which we then convert to f64 278 | while s.n_idx < s.next.len() && (c >= '0' && c <= '9') || c == '.' { 279 | num_str.push(c); 280 | s.n_idx += 1; 281 | if s.n_idx < s.next.len() { 282 | c = s.next.as_bytes()[s.n_idx] as char; 283 | } 284 | } 285 | s.value = f64::from_str(&num_str)?; 286 | s.s_type = ExprType::TOK_NUMBER; 287 | } else { 288 | // look for a variable or builting function call 289 | if next_char >= 'a' && next_char <= 'z' { 290 | let mut txt_str = String::new(); 291 | let mut c = next_char; 292 | 293 | while s.n_idx < s.next.len() && (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') { 294 | txt_str.push(c); 295 | s.n_idx += 1; 296 | if s.n_idx < s.next.len() { 297 | c = s.next.as_bytes()[s.n_idx] as char; 298 | } 299 | } 300 | 301 | let mut var = find_lookup(&s, &txt_str); 302 | if let None = var { 303 | var = find_builtin(&txt_str); 304 | } 305 | 306 | if let Some(v) = var { 307 | match type_mask!(v.v_type) { 308 | ExprType::TE_VARIABLE => { s.s_type = ExprType::TOK_VARIABLE; s.bound = v.address; }, 309 | ExprType::TE_CLOSURE0 => s.context = v.context, 310 | ExprType::TE_CLOSURE1 => s.context = v.context, 311 | ExprType::TE_CLOSURE2 => s.context = v.context, 312 | ExprType::TE_CLOSURE3 => s.context = v.context, 313 | ExprType::TE_CLOSURE4 => s.context = v.context, 314 | ExprType::TE_CLOSURE5 => s.context = v.context, 315 | ExprType::TE_CLOSURE6 => s.context = v.context, 316 | ExprType::TE_CLOSURE7 => s.context = v.context, 317 | ExprType::TE_FUNCTION0 => { s.s_type = v.v_type; s.function = v.function; }, 318 | ExprType::TE_FUNCTION1 => { s.s_type = v.v_type; s.function = v.function; }, 319 | ExprType::TE_FUNCTION2 => { s.s_type = v.v_type; s.function = v.function; }, 320 | ExprType::TE_FUNCTION3 => { s.s_type = v.v_type; s.function = v.function; }, 321 | ExprType::TE_FUNCTION4 => { s.s_type = v.v_type; s.function = v.function; }, 322 | ExprType::TE_FUNCTION5 => { s.s_type = v.v_type; s.function = v.function; }, 323 | ExprType::TE_FUNCTION6 => { s.s_type = v.v_type; s.function = v.function; }, 324 | ExprType::TE_FUNCTION7 => { s.s_type = v.v_type; s.function = v.function; }, 325 | _ => {} 326 | } 327 | } 328 | else { 329 | s.s_type = ExprType::TOK_ERROR; 330 | } 331 | } else { 332 | // look for an operator or special character 333 | match s.next.as_bytes()[s.n_idx] as char { 334 | '+' => { s.s_type = ExprType::TOK_INFIX; s.function = add; }, 335 | '-' => { s.s_type = ExprType::TOK_INFIX; s.function = sub; }, 336 | '*' => { s.s_type = ExprType::TOK_INFIX; s.function = mul; }, 337 | '/' => { s.s_type = ExprType::TOK_INFIX; s.function = div; }, 338 | '^' => { s.s_type = ExprType::TOK_INFIX; s.function = pow; }, 339 | '%' => { s.s_type = ExprType::TOK_INFIX; s.function = fmod; }, 340 | '(' => s.s_type = ExprType::TOK_OPEN, 341 | ')' => s.s_type = ExprType::TOK_CLOSE, 342 | ',' => s.s_type = ExprType::TOK_SEP, 343 | ' ' | '\t' | '\n' |'\r' => {}, 344 | _ => s.s_type = ExprType::TOK_ERROR 345 | } 346 | s.n_idx += 1; 347 | } 348 | } 349 | } 350 | 351 | Ok(String::new()) 352 | } 353 | 354 | fn base(s: &mut State) -> Result { 355 | let mut ret: Expr; 356 | 357 | match type_mask!(s.s_type) { 358 | ExprType::TOK_NUMBER => { 359 | ret = new_expr(ExprType::TE_CONSTANT, None); 360 | ret.value = s.value; 361 | next_token(s)?; 362 | }, 363 | ExprType::TOK_VARIABLE => { 364 | ret = new_expr(ExprType::TE_VARIABLE, None); 365 | ret.bound = s.bound; 366 | next_token(s)?; 367 | }, 368 | ExprType::TE_FUNCTION0 | ExprType::TE_CLOSURE0 => { 369 | ret = new_expr(s.s_type, None); 370 | ret.function = s.function; 371 | // todo: set parameters 372 | /*if is_closure!(s.s_type) { 373 | ret.parameters[0] = s.context[0].clone(); 374 | }*/ 375 | next_token(s)?; 376 | // todo: set parameters 377 | }, 378 | ExprType::TE_FUNCTION1 | ExprType::TE_CLOSURE1 => { 379 | ret = new_expr(s.s_type, None); 380 | ret.function = s.function; 381 | // todo: set parameters 382 | next_token(s)?; 383 | ret.parameters.push(power(s)?); 384 | // todo: set parameters 385 | }, 386 | ExprType::TE_FUNCTION2 | ExprType::TE_CLOSURE2 | ExprType::TE_FUNCTION3 | 387 | ExprType::TE_CLOSURE3 | ExprType::TE_FUNCTION4 | ExprType::TE_CLOSURE4 | 388 | ExprType::TE_FUNCTION5 | ExprType::TE_CLOSURE5 | ExprType::TE_FUNCTION6 | 389 | ExprType::TE_CLOSURE6 | ExprType::TE_FUNCTION7 | ExprType::TE_CLOSURE7 => { 390 | let arity = arity!(s.s_type); 391 | 392 | ret = new_expr(s.s_type, None); 393 | ret.function = s.function; 394 | // todo: set parameters 395 | next_token(s)?; 396 | 397 | if s.s_type != ExprType::TOK_OPEN { 398 | s.s_type = ExprType::TOK_ERROR; 399 | } else { 400 | let mut idx = 0; 401 | for _ in 0..arity { 402 | next_token(s)?; 403 | ret.parameters.push(expr(s)?); 404 | if s.s_type != ExprType::TOK_SEP { 405 | break; 406 | } 407 | idx += 1; 408 | } 409 | if s.s_type != ExprType::TOK_CLOSE || (idx != arity-1) { 410 | s.s_type = ExprType::TOK_ERROR; 411 | } else { 412 | next_token(s)?; 413 | } 414 | } 415 | }, 416 | ExprType::TOK_OPEN => { 417 | next_token(s)?; 418 | ret = list(s)?; 419 | if s.s_type != ExprType::TOK_CLOSE { 420 | s.s_type = ExprType::TOK_ERROR; 421 | } else { 422 | next_token(s)?; 423 | } 424 | } 425 | _ => { 426 | // todo: better error? Use NaN? 427 | ret = new_expr(ExprType::TE_VARIABLE, None); 428 | s.s_type = ExprType::TOK_ERROR; 429 | ret.value = 0.0; 430 | } 431 | } 432 | 433 | Ok(ret) 434 | } 435 | 436 | fn power(s: &mut State) -> Result { 437 | let mut sign = 1; 438 | 439 | while s.s_type == ExprType::TOK_INFIX && (s.function == add || s.function == sub) { 440 | if s.function == sub { sign = -sign; } 441 | next_token(s)?; 442 | } 443 | 444 | let mut ret: Expr; 445 | 446 | if sign == 1 { 447 | ret = base(s)?; 448 | } else { 449 | ret = new_expr(ExprType::TE_FUNCTION1 | ExprType::TE_FLAG_PURE, Some(vec![base(s)?.clone()])); 450 | ret.function = neg; 451 | } 452 | 453 | Ok(ret) 454 | } 455 | 456 | // todo: ifdef TE_POW_FROM_RIGHT 457 | fn factor(s: &mut State) -> Result { 458 | let mut ret = power(s)?; 459 | 460 | // todo: check functions here 461 | while s.s_type == ExprType::TOK_INFIX && s.function == pow { 462 | let f = s.function; 463 | next_token(s)?; 464 | ret = new_expr(ExprType::TE_FUNCTION2 | ExprType::TE_FLAG_PURE, Some(vec![ret.clone(), power(s)?.clone()])); 465 | ret.function = f; 466 | } 467 | 468 | Ok(ret) 469 | } 470 | 471 | fn term(s: &mut State) -> Result { 472 | let mut ret = factor(s)?; 473 | 474 | while s.s_type == ExprType::TOK_INFIX && (s.function == mul || s.function == div || s.function == fmod) { 475 | let f = s.function; 476 | next_token(s)?; 477 | ret = new_expr(ExprType::TE_FUNCTION2 | ExprType::TE_FLAG_PURE, Some(vec![ret.clone(), factor(s)?.clone()])); 478 | ret.function = f; 479 | } 480 | 481 | Ok(ret) 482 | } 483 | 484 | fn expr(s: &mut State) -> Result { 485 | let mut ret = term(s)?; 486 | 487 | while s.s_type == ExprType::TOK_INFIX && (s.function == add || s.function == sub) { 488 | let f = s.function; 489 | next_token(s)?; 490 | ret = new_expr(ExprType::TE_FUNCTION2 | ExprType::TE_FLAG_PURE, Some(vec![ret.clone(), term(s)?.clone()])); 491 | ret.function = f; 492 | } 493 | 494 | Ok(ret) 495 | } 496 | 497 | fn list(s: &mut State) -> Result { 498 | let mut ret = expr(s)?; 499 | 500 | while s.s_type == ExprType::TOK_SEP { 501 | next_token(s)?; 502 | ret = new_expr(ExprType::TE_FUNCTION2 | ExprType::TE_FLAG_PURE, Some(vec![ret.clone(), expr(s)?.clone()])); 503 | ret.function = comma; 504 | } 505 | 506 | Ok(ret) 507 | } 508 | 509 | fn optimize(n: &mut Expr) { 510 | // evaluates as much as possible 511 | if n.e_type == ExprType::TE_CONSTANT { return; } 512 | if n.e_type == ExprType::TE_VARIABLE { return; } 513 | 514 | if (n.e_type & ExprType::TE_FLAG_PURE).bits() != 0 { 515 | let mut _known = 1; 516 | let arity = arity!(n.e_type); 517 | 518 | for _ in 0..arity { 519 | // todo: optimize parameters 520 | } 521 | 522 | if _known != 0 { 523 | n.value = eval(&n); 524 | n.e_type = ExprType::TE_CONSTANT; 525 | } 526 | } 527 | } 528 | 529 | fn compile(expression: &str, variables: Option> ) -> Result> { 530 | let mut s = State::new(expression); 531 | if let Some(vars) = variables { 532 | s.lookup = vars; 533 | } 534 | 535 | arity!(s.s_type); 536 | next_token(&mut s)?; 537 | let mut root = list(&mut s)?; 538 | 539 | if s.s_type != ExprType::TOK_END { 540 | return Ok(None) 541 | } 542 | 543 | optimize(&mut root); 544 | Ok(Some(root)) 545 | } 546 | 547 | /// Interprets a string expression as a mathematical expresion, evaluates it and returns its result. 548 | /// 549 | /// # Examples 550 | /// 551 | /// ``` 552 | /// extern crate tinyexpr; 553 | /// 554 | /// // "result" should contain a "4" 555 | /// let result = tinyexpr::interp("2+2").unwrap(); 556 | /// ``` 557 | pub fn interp(expression: &str) -> Result { 558 | match compile(expression, None) { 559 | Ok(Some(expr)) => Ok(eval(&expr)), 560 | Err(e) => Err(e), 561 | _ => Err(error::TinyExprError::Other(String::from("NaN"))) 562 | } 563 | } 564 | 565 | // todo 566 | fn eval(n: &Expr) -> f64 { 567 | match type_mask!(n.e_type) { 568 | ExprType::TE_CONSTANT => n.value, 569 | ExprType::TE_VARIABLE => n.bound as f64, 570 | ExprType::TE_FUNCTION0 | ExprType::TE_FUNCTION1 | ExprType::TE_FUNCTION2 | ExprType::TE_FUNCTION3 | 571 | ExprType::TE_FUNCTION4 | ExprType::TE_FUNCTION5 | ExprType::TE_FUNCTION6 | ExprType::TE_FUNCTION7 => { 572 | match arity!(n.e_type) { 573 | // todo: REALLY need more function pointer types to avoid hacks like this 0.0 here... 574 | 0 => ((*n).function)(0.0, 0.0), 575 | 1 => ((*n).function)(eval(&n.parameters[0]), 0.0), 576 | 2 => ((*n).function)(eval(&n.parameters[0]), eval(&n.parameters[1])), 577 | _ => panic!("todo: add more f. pointers (type is {})", arity!(n.e_type)) 578 | } 579 | } 580 | _ => 0.0 581 | } 582 | } 583 | -------------------------------------------------------------------------------- /tests/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate tinyexpr; 2 | 3 | #[test] 4 | fn check_basics() { 5 | assert_eq!(tinyexpr::interp("2*2").unwrap(), 4.0); 6 | assert_eq!(tinyexpr::interp("2+2").unwrap(), 4.0); 7 | assert_eq!(tinyexpr::interp("3-2").unwrap(), 1.0); 8 | assert_eq!(tinyexpr::interp("5%2").unwrap(), 1.0); 9 | assert_eq!(tinyexpr::interp("5^2").unwrap(), 25.0); 10 | assert_eq!(tinyexpr::interp("2+2*2").unwrap(), 6.0); 11 | assert_eq!(tinyexpr::interp("(2+2)*2").unwrap(), 8.0); 12 | assert_eq!(tinyexpr::interp("(2+2)*2/2").unwrap(), 4.0); 13 | assert_eq!(tinyexpr::interp("abs(-1)").unwrap(), 1.0); 14 | assert_eq!(tinyexpr::interp("sqrt(728*728)").unwrap(), 728.0); 15 | assert_eq!(tinyexpr::interp("pow(2.0, 3.0)").unwrap(), 8.0); 16 | assert_eq!(tinyexpr::interp("exp(1)").unwrap(), tinyexpr::interp("e").unwrap()); 17 | assert_eq!(tinyexpr::interp("floor(3.1415)").unwrap(), 3.0); 18 | assert_eq!(tinyexpr::interp("ceil(3.1415)*floor(3.1415)").unwrap(), 12.0); 19 | assert_eq!(tinyexpr::interp("5,2").unwrap(), 2.0); 20 | } 21 | 22 | #[test] 23 | fn check_constants() { 24 | assert_eq!(tinyexpr::interp("pi").unwrap(), 3.141592653589793); 25 | assert_eq!(tinyexpr::interp("e").unwrap(), 2.718281828459045); 26 | } 27 | 28 | #[test] 29 | fn check_logarithms() { 30 | assert_eq!(tinyexpr::interp("ln(e)").unwrap(), 1.0); 31 | assert_eq!(tinyexpr::interp("log(10)").unwrap(), 1.0); 32 | assert_eq!(tinyexpr::interp("log10(10)").unwrap(), 1.0); 33 | } 34 | 35 | 36 | #[test] 37 | fn check_trigs() { 38 | assert_eq!(tinyexpr::interp("2*1/sin(3.14/2)").unwrap().round(), 2.0); 39 | assert_eq!(tinyexpr::interp("asin(1)").unwrap(), tinyexpr::interp("pi/2").unwrap()); 40 | assert_eq!(tinyexpr::interp("tan(pi)").unwrap().round(), 0.0); 41 | assert_eq!(tinyexpr::interp("atan(pi/2)").unwrap().round(), 1.0); 42 | assert_eq!(tinyexpr::interp("atan2(pi, 2)").unwrap().round(), 1.0); 43 | assert_eq!(tinyexpr::interp("cos(0)").unwrap().round(), 1.0); 44 | assert_eq!(tinyexpr::interp("acos(1)").unwrap(), 0.0); 45 | } 46 | 47 | #[test] 48 | fn check_hyberbolic_trigs() { 49 | assert_eq!(tinyexpr::interp("sinh(0)").unwrap(), 0.0); 50 | assert_eq!(tinyexpr::interp("cosh(0)").unwrap(), 1.0); 51 | assert_eq!(tinyexpr::interp("tanh(10000)").unwrap(), 1.0); 52 | } 53 | 54 | #[test] 55 | #[should_panic] 56 | fn parse_error() 57 | { let _ = tinyexpr::interp("atan(foo)").unwrap_or_else(|e| { panic!("{}", e); }); } 58 | --------------------------------------------------------------------------------