Yarer (Yet Another Rust Expression Resolver) is a library for evaluating mathematical expressions. Internally it uses the shunting yard algorithm.
Example of usage of the library:
let session = Session::init();
let mut resolver = session.process("1+2"); // or even "(cos(10+e)+3*sin(9/pi))^2"
println!("The result is {}", resolver.resolve().unwrap());All that's needed is to get a new instance of the 'resolver' from a Session and hand over the expression to be analysed.
The library returns a Number, and the value decides which variant, not the expression that produced it. An integral result always comes back as Number::NaturalNumber, whatever it came from — 2.5+2.5 is 5, 1/cos(0) is 1, 6/3 is 2. Number::DecimalNumber appears only when the value genuinely has a fractional part, as in 0.1+0.2 or 1/3. Every mathematical value therefore has exactly one representation.
Yarer handles variables and functions. Here is an example:
let session = Session::init();
let mut resolver = session.process("1/cos(x^2)");
session.set("x",1);
println!("The result is {}", resolver.resolve().unwrap());and of course, the expression can be re-evaluated if the variable changes.
//...
session.set("x",-1);
println!("The result is {}", resolver.resolve().unwrap());
session.setf("x",0.001);
println!("The result is {}", resolver.resolve().unwrap());
//...The result can be converted into an i32 or an f64 (if decimal) using the
fallible TryFrom/TryInto conversions, which return an error instead of
panicking when the value does not fit the target type:
let result: Number = resolver.resolve().unwrap();
let int : i32 = result.clone().try_into().unwrap();
// or
let float : f64 = result.try_into().unwrap();Yarer can also be used from the command line and behaves similarly to GNU bc
$ yarer
Yarer v.0.2.0 - Yet Another Rust Expression Resolver.
License MIT OR Apache-2.0
> (1+9)*(8+2)+0!
101
> (1./2)+atan(10)
1.1483608274590869
> x=10
> 3/sin(5*x^2)
-6.41338354698791
> ln(1)
0
> log(10)
1
> -2^-2
0.25
> 1/(log(10)+cos(0))^-2
4
> 4.5+7.9*2.2
21.88
> 9801/(2206*sqrt(2)) // approx of PI
3.1415927300133055
Yarer 0.2.0 is a correctness-focused release that includes a breaking API change.
Breaking change: conversions from Number to i32, i64, i128 and f64 are now
fallible. They are exposed via TryFrom/TryInto (returning a ConversionError) instead of
the previous panicking From/Into. Conversion to BigInt remains infallible via From.
Update let n: i32 = result.into(); to let n: i32 = result.try_into()?; (or .unwrap()).
Other changes:
- Out-of-range numeric conversions now return an error instead of panicking.
Number→BigIntconversion is exact (truncates the rational toward zero) rather than round-tripping throughf64, so precision is no longer silently lost.- A trailing
;now returns the last segment's value instead of reporting a spurious "malformed expression" error after the assignment already took effect. - Malformed segments inside a
;-chained expression are now rejected instead of being silently discarded. - Stricter parser validation: malformed expressions and unexpected tokens raise a clear error.
- Removed an unused, internally-panicking
BitXorimplementation forNumber.
Yarer 0.1.8 comes with several enhancements:
- Decimal numbers are now represented using the
num-rationalcrate for higher precision. - Added new math functions:
floor,ceil,round,exp,pdfandcdf. - Expressions can be chained with semicolons, e.g.
x=2; y=3; x*y. - Variable assignments inside expressions are handled more reliably.
- This README includes a demonstration of the Black–Scholes formula.
Starting with Yarer version 0.1.7, natural numbers are implemented internally using BigInt from the num crate. Now it is possible to do calculations with arbitrarily large natural numbers.
$ yarer
Yarer v.0.2.0 - Yet Another Rust Expression Resolver.
License MIT OR Apache-2.0
> 78!
1132428117820629783145752115873204622873174957948825.....
> 2^78
302231454903657293676544From Yarer version 0.1.5 it's possible to share a single session, and therefore a single heap of variables, for multiple resolvers. The library is not intended to be thread-safe.
let session = Session::init();
let mut res = session.process("x ^ 2");
let mut res2 = session.process("x! - (x-1)!");
session.set("x", 10);
if let (Ok(a), Ok(b)) = (res.resolve(),res2.resolve()) {
println!("{} {}", a, b); // 100 3265920
}There are several math functions defined that you can use in your expression. More to come! There are many examples of processed expressions in the integration test file.
Sin
Cos
Tan
ASin
ACos
ATan
Ln
Log
Abs
Sqrt
Max
Min
Floor
Ceil
Round
Exp
Pdf
CdfFunction arguments are always parenthesised: sqrt(16), max(1,2).
There are a few predefined math constants available:
PI -> 3.14159265...
e -> 2.7182818...
tau -> 6.2831853...
phi -> 1.6180339...
gamma -> 0.57721566...Using Yarer, the Black–Scholes formula for a European call option can be evaluated straight from the CLI.
$ yarer
Yarer v.0.2.0 - Yet Another Rust Expression Resolver.
License MIT OR Apache-2.0
> S=100;K=100;T=1;r=0.05;sigma=0.2;
> d1=(ln(S/K)+(r+sigma^2/2)*T)/(sigma*sqrt(T))
> d2=d1-sigma*sqrt(T)
> S*cdf(d1)-K*exp(-r*T)*cdf(d2)
10.450583572185565To run it from cargo, just type:
cargo run -q -- For logging debug just run with:
env RUST_LOG=yarer=debug cargo run -q -- or to build and install a release from the code:
cargo build --release
cargo install --path .
./target/release/yarerEach expression is the result of an evaluation by the following actors
Step1 - Parser: A string is "regexed" and converted into a token array.
Step 2 - RpnResolver: Using the Shunting Yard algorithm the token array is converted from infix to postfix notation.
Step 3 - RpnResolver: The resulting RPN (Reverse Polish Notation) expression is evaluated.
It's worth mentioning that the Session is responsible for storing all variables (and constants) that are borrowed by all the RpnResolvers.
Besides being stable, Yarer is a work in progress. If you have suggestions for features (i.e. more math functions to implement), or if you find any issues in the code, design, interface, etc, please feel free to share them on our GitHub.
I appreciate very much your feedback!