Git Product home page Git Product logo

rsc's Introduction

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.

rsc's People

Contributors

atul9 avatar fivemoreminix avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

rsc's Issues

Release 1.2.0 depends on nightly channel

Tried a very simple example in integration test:
assert!(rsc::eval("5^2").unwrap() == 25.0);

Got a compile error:

>cargo test
   Compiling rsc v1.2.0
error[E0554]: #![feature] may not be used on the stable release channel
  --> C:\Users\Youka\.cargo\registry\src\github.com-1ecc6299db9ec823\rsc-1.2.0\src/lib.rs:55:1
   |
55 | #![feature(test)]
   | ^^^^^^^^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0554`.
error: Could not compile `rsc`.

Seems this crate depends on standard benchmarking which is still not stable. Missing the note about this restriction in crate description.

token error. aaa325azxv*1000 -> aaa,325,azxv,*,1000

Hello,
After structuring the code using your library and trying it out, I encountered an error.

For example,
When I input 'asd100/5', it correctly tokenizes into 'asd,,100,/,5', and the calculation works fine.

However, as in the title, the tokens for variable names seem to be incorrect in the following case:
'aaa325azxv1000' -> 'aaa,325,azxv,,1000'

I think modifying only the following part of the tokenize function would fix it.

match c {
            '+' => tokens.push(Token::Operator(Plus)),
            '-' => tokens.push(Token::Operator(Minus)),
            '*' => tokens.push(Token::Operator(Star)),
            '/' => tokens.push(Token::Operator(Slash)),
            '%' => tokens.push(Token::Operator(Percent)),
            '^' => tokens.push(Token::Operator(Caret)),
            '(' => tokens.push(Token::Operator(LParen)),
            ')' => tokens.push(Token::Operator(RParen)),
            '|' => tokens.push(Token::Operator(Pipe)),
            '=' => tokens.push(Token::Operator(Equals)),
            '!' => tokens.push(Token::Operator(Factorial)),
            c => {
                if c.is_whitespace() {
                    continue;
                } else if c.is_digit(10) || c == '.' {
                    let mut number_string = c.to_string(); // Like creating a new string and pushing the character.

                    while let Some(&c) = chars.peek() {
                        if c.is_digit(10) || c == '.' {
                            number_string.push(chars.next().unwrap());
                        } else {
                            break;
                        }
                    }

                    match number_string.parse::<T>() {
                        Ok(num) => tokens.push(Token::Number(num)),
                        _ => return Err(LexerError::InvalidNumber(number_string)),
                    }
                } else if c.is_alphabetic() {
                    let mut full_identifier = c.to_string();

                    while let Some(&c) = chars.peek() {
                        if c.is_alphabetic() || c == '_' {
                            full_identifier.push(chars.next().unwrap());
                        } else {
                            break;
                        }
                    }

                    tokens.push(Token::Identifier((if case_sensitive { full_identifier } else { full_identifier.to_lowercase() }).to_owned()));
                } else {
                    return Err(LexerError::InvalidCharacter(c));
                }
            }
        }

Use a Computer across threads

How do I make it thread-safe? I want to use a Computer across threads using std::sync::Arc, but the error says missing lifetime specifier. If I write 'static, it says help: the trait 'std::marker::Sync' is not implemented for 'dyn std::ops::Fn(f64) -> f64 + 'static)'

Support for boolean operators

Hi I noticed that you added initial support for boolean operators in this commit: 2b1ed6a

It appears that in your version 3 rewrite this support was lost. Do you have plans to add support for boolean operations or did you find some blockers in the way the code is correctly structured? What would be the blockers in adding boolean operators into version 3?

Many thanks for a great library.

Need To Decide a Syntax For Functions

Since functions having a syntax like func var1 (var2) poses two problems:

  1. How do we know var1 isn't a function, since it has parenthesis around var2; presumably a variable since it has no potential arguments, and then, assuming var1 is the function, that must mean func is too since it has an argument. But we were only multiplying var1 by var2! We need to find a way to determine this.
  2. This determination needs to be done at computation time, therefore we have to export some information from the interpreter to the computer for it to determine what is a function call and what is not.

We could potentially solve this by having the parser export potential discrepencies (identifier followed by parenthetical expressions) as FunctionOrVariable with its variable name and expression tree, which the computer can possibly index the variables and functions maps to see if there is a function that matches, or if there is a variable, and select one of the two, probably preferring functions.

Since as of posting this issue, computation time only accounts for about 500 ns of time out of the 7.5 microseconds of time the entire evaluation takes, implementing this in the computer should not be such a bad idea, or add much delay.

Implement Percentages

Expressions like 2 * 5% would translate to 2 * (5/100). Percentages can only be appended to factors and just divides them by 100.

Thank you for writing this.

Hi @fivemoreminix (Luke)

I am going to publish typenum-consts, which consists of 4 procedural macros that converts a literal integer to type-level integers provided by the typenum crate.

Within it, I allow for evaluation of simple mathematical expressions, like so:

use typenum_consts::uconst;
use typenum::{U15, assert_type_eq);

type A = uconst![{
    a = 5;
    b = 10;
    a + b; // a + b = 5 + 10 = 15 => `A` is `U15`
}];
assert_type_eq!(U15, A);

I used your crate rsc as the evaluator. Thank you for building this.

I vendored your crate because you didn't put it on crates.io and I needed your crate to support implementing Num for isize.

Thank you.

Log2 gives wrong answers

If I do log2(18000) it results in 5418.54 instead of 14.13 like rustlang f32::log2() or a scientific calculator

Issue with parser

>sqrt 3 / sqrt 5
Function(
    Sqrt,
    Constant(
        3.0
    )
)
1.7320508075688772
>2 + sqrt(3)
Constant(
    2.0
)
2

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.