Skip to content

A handwritten scientific calculator for interpreting equations.

License

Notifications You must be signed in to change notification settings

skycloudd/rscalc

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RSCALC, the Calculator for Rust Code

RSCALC is a handwritten scientific calculator for interpreting equations inside strings. RSCALC is designed to do a single thing very well, enabling anyone to extend it with more features.

RSCALC intends to beat Wirth's Law. Therefore, RSCALC will not receive many additions. It will still receive updates with relation to efficiency.

Library

use rscalc::{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 rscalc::{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 rscalc::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 RSCALC 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

RSCALC 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 rscalc directly:

rscalc "12/sqrt(128)" > result.txt

There are various flags you can pass. Try:

rscalc -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

RSCALC 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 RSCALC. It will also forever keep the same open-source permissions.

License

RSCALC is MIT licensed. RSCALC will always remain free to modify and use without attribution.

About

A handwritten scientific calculator for interpreting equations.

Resources

License

Stars

Watchers

Forks

Languages

  • Rust 100.0%