fivemoreminix / rsc

A handwritten scientific calculator for interpreting equations.

Home Page:https://crates.io/crates/rsc

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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.

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)" > result.txt

There are various flags you can pass. Try:

rsc -tev

Notes About Performance

  • The lexer is iterative but could 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. But, I plan to replace it with an LR(2) operator-precedence parser, which would be much more 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, providing better performance where recursive calls are found.
  • 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 tastes 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.

About

A handwritten scientific calculator for interpreting equations.

https://crates.io/crates/rsc

License:MIT License


Languages

Language:Rust 100.0%