Saturday, October 27, 2012

A Java eval() Implementation

I recently came across a situation where I had to evaluate expressions that the user had input, in Java there are quite a few ways to do this, but the solutions had several downsides:
  • Resource hungry (3mb libraries that add to JAR file size greatly)
  • RAM hungry, some store strings indefinitely
  • Insecure, compile code using JVM then load it as a class
  • Difficult to integrate
After working with Mozilla's Rhino for around eight months, it became burdensome and I wrote my own interpreter that:
  • Can do standard operations */+-()
  • Can optionally run "functions" or use "variables"
  • Is fast, as fast as Rhino for small one-off expressions
  • Is small (< 7Kb compiled, before being put in a JAR)
The code is on Github, under a BSD license.

Running it is quite simple, just download the jar, add it to your project, and use the following examples as guides

Simple Example:

import net.josephlewis.jeval.Eval;
import net.josephlewis.jeval.MalformedExpression;

public class BasicExample {
public static void main(String[] args)
{
// A simple expression.
try {
System.out.println( Eval.eval("2 * 3 + 5") );
} catch (MalformedExpression e) {
System.err.println(e.getMessage());
}

// Check to see if an expression is valid or not.
System.out.println("is valid? (should be true)");
System.out.println(Eval.isErrorFree("2 * 3 + 5", null));

System.out.println("is valid? (should be false)");
System.out.println(Eval.isErrorFree("2 * 3 +", null));
}
}

Example With Variables/Function Calls:

import net.josephlewis.jeval.Eval;
import net.josephlewis.jeval.MalformedExpression;
import net.josephlewis.jeval.VariableStore;

public class VariableStoreExample implements VariableStore {

/**
* Not a very useful getValue, just summs the ASCII value of the given
* string and returns it.
*/
@Override
public double getValue(String variable) throws MalformedExpression {
double b = 0;

for(char c : variable.toCharArray())
b += Character.getNumericValue(c);

return b;
}

public static void main(String[] args)
{
VariableStore vse = new VariableStoreExample();

try {
System.err.println(Eval.eval("$a - $a", vse));
System.err.println(Eval.eval("$abc - $cba", vse));

// Spaces can be included in variable names, if they're enclosed
// within ( and ) or "s
System.err.println(Eval.eval("$(hello world) - $(world hello)", vse));
System.err.println(Eval.eval("$hello(world) - $world(hello)", vse));
System.err.println(Eval.eval("$hello(\"world\") - $world(\"hello\")", vse));

} catch (MalformedExpression e) {
e.printStackTrace();
}
}
}

And there you have it, a custom parser for expressions as simple as just */+-() or as complex as you want through extensions using dollar signs.

After using the old JS parser for over a year in the before-mentioned project this was a breath of fresh air, the JAR file that held Rhino shrunk by a few MB providing relief for my users and the server that had to send it around.

No comments:

Post a Comment