RSC, the Calculator for Rust Code ================================= ![](https://img.shields.io/crates/l/rsc.svg) ![](https://img.shields.io/badge/status-stable-blue.svg) **New**: crate updated to 3.0, read the [Changelog](CHANGELOG.md). **RSC is a handwritten scientific calculator for interpreting equations inside strings.** RSC is designed to do a single thing very well, enabling anyone to extend it with more features. RSC intends to beat Wirth's Law. **Therefore, RSC will not receive many additions.** It will still receive updates with relation to efficiency. ## Library ```rust use rsc::{tokenize, parse, Interpreter}; // Maybe you write a wrapper function fn evaluate(input: &str, interpreter: &mut Interpreter) -> Result { // You have to call each function in the pipeline, but this gives you the most // control over error handling and performance. match tokenize(input) { // Step 1: splits input into symbols, words, and numbers Ok(tokens) => match parse(&tokens) { // Step 2: builds an Expr using tokens Ok(expr) => match interpreter.eval(&expr) { // Step 3: interprets the Expr Ok(result) => println!("{}", result), Err(interpret_error) => eprintln!("{:?}", interpret_error), }, Err(parse_error) => eprintln!("{:?}", parse_error), }, Err(tokenize_error) => eprintln!("{:?}", tokenize_error), } } fn main() { // Constructs an f64 interpreter with included variables let mut interpreter = Interpreter::default(); evaluate("5^2", &mut interpreter); // prints "25" evaluate("x = 3", &mut interpreter); // prints "3" evaluate("x(3) + 1", &mut interpreter); // prints "10" } ``` Variables are stored in the `Interpreter`: ```rust use rsc::{tokenize, parse, Interpreter, Variant, InterpretError}; // assume you still had your evaluate function above fn main() { // Create a completely empty interpreter for f64 calculations let mut i = Interpreter::::new(); // Create some variables i.set_var(String::from("pi"), Variant::Num(std::f64::consts::PI)); i.set_var(String::from("double"), Variant::Function(|name, args| { if args.len() < 1 { Err(InterpretError::TooFewArgs(name, 1)) } else if args.len() > 1 { Err(InterpretError::TooManyArgs(name, 1)) } else { Ok(args[0] * 2) // get the only argument and double it } })); evaluate("double(pi)", &mut i); // prints "6.283185307179586" } ``` Because it can be redundant checking that functions received the correct number of arguments (if you wish to do so at all), I made a helper function called `ensure_arg_count`. The above function redefined: ```rust use rsc::ensure_arg_count; i.set_var(String::from("double"), Variant::Function(|name, args| { // return Err if args are not within the min and max count ensure_arg_count(1, 1, args.len(), name)?; Ok(args[0] * 2) })); ``` ## Executable ### First you might need to build RSC as an executable ```shell cargo build --release --features=executable ``` 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. ### Usage ```shell RSC interactive expression interpreter. Try "help" for commands and examples. >sqrt(15+3) :4.242640687119285 >:square root >sqrt(15, 3) Function "sqrt" received more than the maximum 1 argument. > |-5| :5 >abs(-5) :5 >sqrt(4)(2) ^ UnexpectedToken(Token { value: Symbol(LP), span: 7..8 }) >(sqrt(4))(2) :4 >x = 1.24 :1.24 >x(4) :4.96 >vars factorial(..) sqrt(..) abs(..) x = 1.24 e = 2.718281828459045 pi = 3.141592653589793 tau = 6.283185307179586 ``` Expressions can be passed to rsc directly: ```shell $ rsc "12/sqrt(128)" 1.0606601717798212 ``` There are various flags you can pass. Try: ```shell rsc -tev ``` ```shell $ rsc -h rsc 3.0.0 A scientific calculator for the terminal. USAGE: rsc [FLAGS] [expr] FLAGS: -e, --expr Prints the expression tree -h, --help Prints help information --no-color Prevents colored text -t, --tokens Prints the tokens -v, --vars Prints variable map -V, --version Prints version information ARGS: ``` ## Notes About Performance * The lexer is iterative and can easily be optimized. * 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. * 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. * Performance improvement PRs are very much welcomed and probably easy! ## Stability 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. ## License RSC is MIT licensed. RSC will always remain free to modify and use without attribution.