12 releases (6 stable)
3.0.0 | Mar 31, 2024 |
---|---|
2.0.0 | Jun 17, 2019 |
1.2.1 | Jun 16, 2019 |
1.2.0 | Jan 1, 2019 |
0.1.5 | Aug 28, 2018 |
#61 in Math
505 downloads per month
Used in typenum-consts
37KB
861 lines
RSC, the Calculator for Rust Code
New: crate updated to 3.0, read the Changelog.
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
use rsc::{tokenize, parse, Interpreter};
// Maybe you write a wrapper function
fn evaluate(input: &str, interpreter: &mut Interpreter<f64>) -> Result<f64, ()> {
// 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
:
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::<f64>::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:
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
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
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:
$ rsc "12/sqrt(128)"
1.0606601717798212
There are various flags you can pass. Try:
rsc -tev
$ 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:
<expr>
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.
Dependencies
~0–10MB
~53K SLoC