1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
//! `xxcalc` is a crate which provides an embeddable floating-point //! polynomial calculator and linear solver. //! //! You can use `xxcalc` to embed mathematical expression evaluator //! in your own programs. By default the calculator provides addition `+`, //! subtraction `-`, multiplication `*`, division `/` and exponentation `^` //! operators. Some functions such as `log` and `log10` are implemented, //! as well as `pi` and `e` constants. This is a polynomial calculator, so //! you can perform arithmetic operations on polynomials (using `x` symbol //! by default) - a simple floating-point number is just a special case of //! a polynomial (the constant polynomial, polynomial of degree zero). //! //! Furthermore the crate is well structurized and documented - you can use //! pieces it provides to create your own custom solutions (you can use or change //! a tokenizer, parser and evaluator however you want). Look at implementations //! of PolynomialParser, LinearSolverParser, PolynomialEvaluator or //! LinearSolverEvaluator to see how to extend default implementations. //! //! With the crate a `xxcalc` binary is provided which can be used as a //! standalone CLI calculator (which can be replacement for `bc` command). //! If the crate is installed with a feature "interactive" enabled, a history //! of commands is enabled. //! //! Whole library is meticulously tested, use `cargo test` to run unit tests //! (including doc examples), `cargo bench` will run simple benchmarks, while //! `cargo bench -- --ignored` will run more computation-heavy benchmarks. //! //! # Examples //! //! You can easily take a LinearSolver or PolynomialCalculator to embed //! mathematical engine inside your programs. //! //! ``` //! use xxcalc::linear_solver::LinearSolver; //! use xxcalc::calculator::Calculator; //! use xxcalc::polynomial::Polynomial; //! //! assert_eq!(LinearSolver.process("2x + 1 = 2(1-x)"), Ok(Polynomial::constant(0.25))); //! assert_eq!(LinearSolver.process("0.1*-2+4"), Ok(Polynomial::constant(3.8))); //! ``` //! //! Please see documentation for [PolynomialCalculator] for more examples. //! //! [PolynomialCalculator]: polynomial_calculator/index.html pub mod polynomial; #[macro_use] pub mod tokenizer; pub mod parser; pub mod evaluator; pub mod calculator; pub mod polynomial_calculator; pub mod linear_solver; use std::ops::Index; use std::collections::BTreeMap; /// Token is a basic unit returned after tokenization using /// a StringProcessor. /// /// Token can be than rearranged with a TokensProcessor and /// interpreted (evaluated) to a Polynomial using a TokensReducer. /// /// Tokens are used to conveniently store expressions in partially /// parsed and computer readable form. #[derive(Debug, PartialEq, Clone)] pub enum Token { /// Opening bracket `(` or beginning of subexpression BracketOpening, /// Closing bracket `)` or ending of subexpression BracketClosing, /// Argument separator Separator, /// Floating point number Number(f64), /// Single character operator Operator(char), /// Symbol identifier (represented by index in identifiers table) Identifier(usize), /// Unidentified character (with no meaning to a StringProcessor) Unknown(char), /// Skip marker Skip } /// List of tokens with their position. /// /// Tokens are represented as space-efficient continous vector /// of tuples consisting of position in a input string and a /// corresponding Token. pub type TokenList = Vec<(usize, Token)>; /// Vector of unique identifiers. /// /// Token::Identifier stores just a location in this vector, /// this keeps size of individual tokens minimal. pub type Identifiers = Vec<String>; /// Tokens is a compound storage for list of tokens with /// their identifiers. /// /// A TokenList is invalid without Identifiers, therefore /// this struct keeps them always together. Furthermore /// a lookup table is kept, so creating new identifier is /// a relatively quick task. #[derive(Debug)] pub struct Tokens { /// List of tokens pub tokens: TokenList, /// Identifier strings used in Token::Identifier pub identifiers: Identifiers, lookup: BTreeMap<String, usize> } /// Transforms text expression into list of tokens. /// /// A StringProcessor can be implemented for a tokenizer (lexer) /// which converts some text expression into a list of tokens. pub trait StringProcessor { fn process(self: &mut Self, line: &str) -> &Tokens; } /// Transforms list of tokens into another list of tokens. /// /// A TokensProcessor can be implemented for a parser which /// takes a list of tokens in some order and transform them /// into list of tokens in the other form (like infix to /// RPN notation). Furthermore a TokensProcessor can be used /// to extend tokenizer with knowledge of new tokens, as it /// can transform unknown tokens to known ones. pub trait TokensProcessor { fn process(&mut self, tokens: &Tokens) -> Result<&Tokens, ParsingError>; } /// Evaluates list of tokens into a single Polynomial value. /// /// A TokensReducer takes a list of tokens in some expected /// arrangement and evaluates them into a single Polynomial /// value. It can be used for implementation of various /// computational languages (such as the scientific calcualtor). pub trait TokensReducer { fn process(&self, tokens: &Tokens) -> Result<polynomial::Polynomial, EvaluationError>; } /// Returns position and token with given index. /// /// One should note that token position is indepentent /// from its index. /// /// # Panics /// /// It will panic when the index is greater than number of /// tokens stored in the list. /// /// # Examples /// /// ``` /// # use xxcalc::{Tokens, Token}; /// let mut tokens = Tokens::new(None); /// /// tokens.push(0, Token::Number(1.0)); /// tokens.push(8, Token::Number(2.0)); /// /// assert_eq!(tokens[0], (0, Token::Number(1.0))); /// assert_eq!(tokens[1], (8, Token::Number(2.0))); /// ``` impl Index<usize> for Tokens { type Output = (usize, Token); fn index(&self, idx: usize) -> &(usize, Token) { &self.tokens[idx] } } impl Tokens { /// Creates a new token storage with optional capacity of /// identifiers. pub fn new(i_cap: Option<usize>) -> Tokens { Tokens { tokens: TokenList::with_capacity(10), identifiers: Identifiers::with_capacity(i_cap.unwrap_or(0)), lookup: BTreeMap::new() } } /// Clears tokens list, however underlying identifiers vector and /// its lookup cache is not cleared, as identifiers may be reused. #[inline(always)] pub fn clear(&mut self) { self.tokens.clear() } /// Pushes a token (with its position) to the end of token list. /// /// # Examples /// /// ``` /// # use xxcalc::{Tokens, Token}; /// let mut tokens = Tokens::new(None); /// /// tokens.push(0, Token::Number(1.0)); /// assert_eq!(tokens.tokens.len(), 1); /// /// tokens.push(1, Token::Number(2.0)); /// assert_eq!(tokens.tokens.len(), 2); /// ``` #[inline(always)] pub fn push(&mut self, position: usize, token: Token) { self.tokens.push((position, token)) } /// Pushes a identifier (with its position) to the end of token list. /// /// Using a lookup cache, an offset in identifier table is obtained. /// If identifier does not yet exist, it is added both to the identifier /// vector and to the lookup cache. Identifiers are case-agnostic. /// A Token::Identifier with appropriate offset is pushed to the end /// of the token list. /// /// # Examples /// /// ``` /// # use xxcalc::{Tokens, Token}; /// let mut tokens = Tokens::new(None); /// /// tokens.push_identifier(0, &String::from("FOO")); /// assert_eq!(tokens[0], (0, Token::Identifier(0))); /// tokens.push_identifier(4, &String::from("foo")); /// assert_eq!(tokens[1], (4, Token::Identifier(0))); /// tokens.push_identifier(8, &String::from("bar")); /// assert_eq!(tokens[2], (8, Token::Identifier(1))); /// assert_eq!(tokens.identifiers, ["foo", "bar"]); /// ``` #[inline] pub fn push_identifier(&mut self, position: usize, value: &String) { let idx = if let Some(&id) = self.lookup.get(value) { id } else { if let Some(&id) = self.lookup.get(&value.to_lowercase()) { let _ = self.lookup.insert(value.clone(), id); id } else { let _ = self.lookup.insert(value.to_lowercase(), self.identifiers.len()); self.identifiers.push(value.to_lowercase()); self.identifiers.len() - 1 } }; self.push(position, Token::Identifier(idx)); } } /// An error that occurs during token processing, /// usually by a parser. #[derive(Debug, PartialEq)] pub enum ParsingError { /// Encountered an operator which is not recognized by a parser. /// The operator is a first argument, while its position is the last one. UnknownOperator(char, usize), /// Encountered a Token with no meaning to the parser, /// probably a Token::Unknown. This token is a first argument, while its /// position is the last one. UnexpectedToken(Token, usize), /// Detected a missing opening or closing bracket at given position. MissingBracket(usize), /// Provided token list is empty, where expected it shouldn't be so. EmptyExpression } /// An error that occurs during token reduction, /// usually by an evaluator. #[derive(Debug, PartialEq)] pub enum EvaluationError { /// Encountered an operator, a constant or a function with no meaning /// to the evaluator. Most probably such symbol must be registered /// beforehand. The identifier is a first argument, while its position /// is the last one. UnknownSymbol(String, usize), /// Number of provided arguments for a function or an operator are not /// enough (the function is registered with larger arity). The symbol /// is a first argument, expected arity is a second argument, while the /// position is the last one. ArgumentMissing(String, usize, usize), /// Multiple expressions where encountered, where not expected (as in /// too many arguments or too many complete expressions). MultipleExpressions, /// A symbol with given name is conflicting with already registered /// function or constant. ConflictingName(String), /// Wraps polynomial error which could be encountered during the evaluation /// as result of some mathematical operation. PolynomialError(polynomial::PolynomialError), /// Wraps linear solver error which could be encountered when assumptions /// needed for solving are not met. SolvingError(linear_solver::SolvingError), /// Used when exponent is of greater degree than zero. Result of such /// exponentiation would not be a polynomial anylonger. NonConstantExponent, /// Used when exponent is not a natural number, while the base of /// exponentiation is not a constant. Result of such operations would /// not be a polynomial anylonger. NonNaturalExponent }