GithubHelp home page GithubHelp logo

curta's Introduction

Curta   Build Status

A small, customizable expression evaluator. Named after the mechanical calculator introduced by Curt Herzstark in 1948.

The parser is generated using JavaCC. The generated parser classes are not a part of this repository, only the grammar is included. The parser source files are being generated by Maven.

It supports all static methods, and variables, from Java 7's Math class and can be easily extended with extra functions. Even certain expressions can be programmatically changed. For example, changing the meaning of the ^ as a binary XOR operator to act as a power operator (see Changing expressions below).

Installation

Clone this repository and run: mvn install which will create a JAR of Curta in your local Maven repository, as well as in the project's target/ folder.

Quick demo

For a more thorough explanation of this library, scroll to the next paragraph, but for the impatient, here's a quick demo of how to use Curta:

Curta curta = new Curta();

// logical
assertEquals( curta.eval("false || true"), true );
assertEquals( curta.eval("(true || false) && true"), true );

// equality
assertEquals( curta.eval("1 == 1.0"), true );
assertEquals( curta.eval("1 != 0.99999"), true );

// relational
assertEquals( curta.eval("-1 < -0.99999"), true );
assertEquals( curta.eval("1 <= 0.99999"), false );

// binary
assertEquals( curta.eval("~8"), -9L );
assertEquals( curta.eval("4 << 1"), 8L );

// basic arithmetic
assertEquals( curta.eval("1 + 2 * 3"), 7.0 );
assertEquals( curta.eval("(1 + 2) * 3"), 9.0 );

// variables
curta.addVariable("mu", 42);
assertEquals( curta.eval("mu + mu"), 84.0 );
assertEquals( curta.eval("return mu + mu"), 84.0 );
assertEquals( curta.eval("foo = 2; mu + foo"), 44.0 );

// built-in functions
assertEquals( curta.eval("abs(-999)"), 999.0 );
assertEquals( curta.eval("cos(PI)"), -1.0 );
assertEquals( curta.eval("hypot(3, 4)"), 5.0 );

// custom function
curta.addFunction(new Function("thrice") {
    @Override
    public Object eval(Object... params) {
        return super.getDouble(0, params) * 3;
    }
});
assertEquals( curta.eval("thrice(9)"), 27.0 );

// change existing expressions
curta.setExpression(Operator.Add, new Expression() {
    @Override
    public Object eval(CurtaNode ast,
                       Map<String, Object> vars,
                       Map<String, Function> functions,
                       Map<Integer, Expression> expressions) {

        // from now on N + M will become N * M
        return super.evalChildAsDouble(0, ast, vars, functions, expressions) *
                super.evalChildAsDouble(1, ast, vars, functions, expressions);
    }
});
assertEquals( curta.eval("3 + 5"), 15.0 );

// reset variables, functions and expressions
curta.clear();
assertEquals( curta.eval("3 + 5"), 8.0 );

Data types

The following data types are supported:

  • number (double and long)
  • boolean (true and false)
  • null (null)

Operators

Below follows a list of all supported operators, starting with the ones with the highest precedence.

  1. grouped expressions: ( ... )
  2. power: **
  3. bitwise not: ~
    • unary minus: -
    • unary plus: +
    • not: !
    • multiply: *
    • division: /
    • modulus: %
    • add: +
    • subtract: -
    • signed left shift: <<
    • signed right shift: >>
    • unsigned right shift: >>>
    • less than: <
    • less than or equal: <=
    • greater than: >
    • greater than or equal: >=
    • equal: ==
    • not equal: !=
  4. bitwise AND: &
  5. bitwise XOR: ^
  6. bitwise OR: |
  7. AND: &&
  8. OR: ||

Note that binary expressions are all evaluated from left to right. This means that an expression like 234, 2**3**4, is evaluated as (2**3)**4. Use parenthesis to let it evaluate from right to left: 2**(3**4)

Variables

The variables PI and E from Java 7's Math class are built-in, and you can assign variables yourself too, either programmatically, or in the input that is to be evaluated. Some examples:

Curta curta = new Curta();

System.out.println(curta.eval("PI"));
System.out.println(curta.eval("E"));

curta.addVariable("answer", 21);

System.out.println(curta.eval("answer * 2"));

System.out.println(curta.eval("x = 5; answer * x"));

which will print:

3.141592653589793
2.718281828459045
42.0
105.0

As you can see in the previous example, you can evaluate more than one statement/expression at the same time. The last expression is returned by Curta's eval(...) method. You can either evaluate Strings, or Files. If you evaluate more than one statement/expression, you need to separate them with a semi colon, ';', or a line break.

Functions

Built-in functions

All methods from Java 7's Math class are supported. However, there's no need to put Math. in front of it.

Some examples:

Curta curta = new Curta();

System.out.println(curta.eval("sin(1)"));
System.out.println(curta.eval("random()"));
System.out.println(curta.eval("hypot(3, 4)"));
System.out.println(curta.eval("abs(-1)"));
System.out.println(curta.eval("min(42, -11)"));

would print:

0.8414709848078965
0.6805327383776745
5.0
1.0
-11.0

(of course, the call to random() is most likely to result in something different... :))

Custom functions

You can also define your own functions programmatically. For example, you would like to create a function that will return true (or false) if a number is prime or not.

You can use Curtas addFunction(Function) method for this:

Curta curta = new Curta();

curta.addFunction(new Function("isPrime") {

    @Override
    public Object eval(Object... params) {

        // number of parameters must be exactly 1 (min=1, max=1)
        super.checkNumberOfParams(1, 1, params);

        // expecting an integer value (not floating-point!)
        long value = super.getLong(0, params);

        // return whether this number is prime
        return new java.math.BigInteger(String.valueOf(value)).isProbablePrime(20);
    }
});

String expression = "isPrime(2147483647)";

System.out.printf("%s = %s\n", expression, curta.eval(expression));

which will print: isPrime(2147483647) = true

As you can see, the abstract class Function, which your custom function must be extended from, has a couple of utility methods to check the number of parameters, and convert these parameters to more usable classes (a long, in this case). This means that evaluating either "isPrime(true)" or "isPrime(11, 17)" would result in an exception to be thrown.

Changing expressions

Some expressions can be changed (or removed) from the parser programmatically. Let's say you don't want to have support for the bitwise-not operator. You can then simply set the operator of an expression to null.

For example, the following code:

Curta curta = new Curta();
System.out.println(curta.eval("~42"));

would print -43, but:

Curta curta = new Curta();
curta.setExpression(Operator.BitNot, null);
System.out.println(curta.eval("~42"));

would throw the following exception:

Exception in thread "main" java.lang.RuntimeException: not implemented: ~ (BitNot)

One more example. Let's say you want to support final variables. Whenever a variable consists of only capital letters (or underscores), you don't want that variable to ever change. You could do this by overriding the default assignment-expression like this:

Curta curta = new Curta();

curta.setExpression(Operator.Assign, new Expression() {
    @Override
    public Object eval(CurtaNode ast, Map<String, Object> vars, Map<String, Function> functions, Map<Integer, Expression> expressions) {

        // get the text of the 1st child
        String id = super.getTextFromChild(0, ast);

        // evaluate the 2nd child
        Object value = super.evalChild(1, ast, vars, functions, expressions);

        // check if it's made from caps only, and is already defined
        if(id.matches("[A-Z_]+") && vars.containsKey(id)) {
            System.err.println("cannot reassign: " + id);
        }
        else {
            vars.put(id, value);
        }

        return value;
    }
});

System.out.println(curta.eval("PI = 3; VAR = 42; VAR = -1; VAR"));

The code above will return 42 and causes the following to be printed to the system.err:

cannot reassign: PI
cannot reassign: VAR

Changing VAR to var will cause -1 to be returned.

See the Operator enum to find out which operators can be reassigned.

Calling clear() on the Curta instance will reset everything: the bitwise-not is supported again, and you can reassign capitalized variables.

curta's People

Contributors

bkiers avatar

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.