Shahzad Bhatti

November 14, 2007

Evaluating dynamic mathematical expressions in Java/Javascript

Filed under: Computing — admin @ 2:23 pm

Recently, I was looking for a tool to evaluate some mathematical expressions in Java, which were stored in some configuration file. I like dynamic features of languages like Ruby, Python, Javascript or even Erlang to evaluate snippet of code dynamically. Since, Java does not support this inherently, I tried looking for some scripting language that I can embed inside Java. Also, I needed a scripting language that provides two-way integration, i.e., ability to call scripting language from Java and an ability to call Java from the scripting language. So I chose Rhino implementation of Javascript which met those goals. Here is a small example that shows how you can do that using Java and Javascript:

  1
  2 import java.util.Collection;
  3 import java.util.ArrayList;
  4 import java.util.HashSet;
  5 import org.mozilla.javascript.*;
  6 import java.math.BigDecimal;
  7 import junit.framework.TestCase;
  8 import junit.framework.Test;
  9 import junit.framework.TestSuite;
 10
 11
 12
 13 public class JsEvalTest extends TestCase {
 14     private static final String FUNCS_PREFIX = "funcs";
 15     private Context cx;
 16     private Scriptable scope;
 17
 18     public void setUp() throws Exception {
 19         cx = Context.enter();
 20         cx.setLanguageVersion(Context.VERSION_1_2);
 21         scope = cx.initStandardObjects();
 22         ScriptableObject.defineClass(scope, Functions.class);
 23         Scriptable funcs = cx.newObject(scope, "Functions", new Object[] {new ArrayList()});
 24         scope.put(FUNCS_PREFIX, scope, funcs);
 25     }
 26
 27     public void tearDown() {
 28         Context.exit();
 29     }
 30
 31     public void testFormula() throws Exception {
 32         String formula = "cpp = cp / spr";
 33         addDependentVariables(formula);
 34         cx.evaluateString(scope, formula, "<cmd>", 1, null);
 35         Object varValue = scope.get("cpp", scope);
 36         assertEquals(new Double(0.5), varValue);
 37     }
 38
 39     public void testFunction() throws Exception {
 40         String formula = "a = " + FUNCS_PREFIX + ".actuals(cp, spr)";
 41         addDependentVariables(formula);
 42         cx.evaluateString(scope, formula, "<cmd>", 1, null);
 43         Object varValue = scope.get("a", scope);
 44         assertEquals(new Double(20000), varValue);
 45     }
 46
 47     private BigDecimal getValueFor(String var) {
 48         if ("cp".equals(var)) return new BigDecimal(100);
 49         else if ("spr".equals(var)) return new BigDecimal(200);
 50         else return null;
 51     }
 52
 53     public static Test suite() {
 54         TestSuite suite = new TestSuite();
 55         suite.addTestSuite(JsEvalTest.class);
 56         return suite;
 57     }
 58
 59     /////////////////////////////////////////////////////////////////
 60     //
 61     private void addDependentVariables(String formula) {
 62         Collection<String> dependentVars = getDependentVars(formula);
 63         for (String dependentVar : dependentVars) {
 64             Object dependentValue = getValueFor(dependentVar);
 65             if (dependentValue != null) scope.put(dependentVar, scope, dependentValue);
 66         }
 67     }
 68     private Collection<String> getDependentVars(String formula) {
 69         CompilerEnvirons compilerEnv = new CompilerEnvirons();
 70         compilerEnv.initFromContext(cx);
 71         compilerEnv.setGeneratingSource(false);
 72         Parser p = new Parser(compilerEnv, compilerEnv.getErrorReporter());
 73         ScriptOrFnNode root = p.parse(formula, null, 1);
 74         return getDependentVars(root);
 75     }
 76
 77     private Collection<String> getDependentVars(final ScriptOrFnNode tree) {
 78         Collection<String> vars = new HashSet<String>();
 79         getDependentVars(tree, tree, vars);
 80         return vars;
 81     }
 82
 83     private void getDependentVars(final ScriptOrFnNode tree, final Node parent, Collection<String> vars) {
 84         Node node = null;
 85         siblingLoop:
 86         for (;;) {
 87             Node previous = null;
 88             if (node == null) {
 89                 node = parent.getFirstChild();
 90             } else {
 91                 previous = node;
 92                 node = node.getNext();
 93             }
 94             if (node == null) {
 95                 break;
 96             }
 97
 98             int type = node.getType();
 99             if (type == Token.NAME) {
100                 String className = node.getClass().getName();
101                 if ("org.mozilla.javascript.Node$StringNode".equals(className)) {
102                     vars.add(node.getString());
103                 }
104             }
105             getDependentVars(tree, node, vars);
106         }
107     }
108
109
110     public static void main(String[] args) {
111         junit.textui.TestRunner.run(suite());
112     }
113 }
114 ~
115

One gotcha, I came across with Rhino was that I also needed to know the input variables to expressions so that I can bind them properly before executing them. Luckily, Rhino is open source and I looked at the source code and hacked a way to parse the expression and find all named variables. For production, I am caching it, but it’s pretty simple to do. For example:

 77     private Collection<String> getDependentVars(final ScriptOrFnNode tree) {
 78         Collection<String> vars = new HashSet<String>();
 79         getDependentVars(tree, tree, vars);
 80         return vars;
 81     }
 82 
 83     private void getDependentVars(final ScriptOrFnNode tree, final Node parent, Collection<String> vars) {
 84         Node node = null;
 85         siblingLoop:
 86         for (;;) {
 87             Node previous = null;
 88             if (node == null) {
 89                 node = parent.getFirstChild();
 90             } else {
 91                 previous = node;
 92                 node = node.getNext();
 93             }
 94             if (node == null) {
 95                 break;
 96             }
 97 
 98             int type = node.getType();
 99             if (type == Token.NAME) {
100                 String className = node.getClass().getName();
101                 if ("org.mozilla.javascript.Node$StringNode".equals(className)) {
102                     vars.add(node.getString());
103                 }
104             }
105             getDependentVars(tree, node, vars);
106         }
107     }
108 

Another nice thing about Javascript is that you can not only call regular Java classes, but can also define Javascriptish methods, e.g.


import org.mozilla.javascript.*;

public class Functions extends ScriptableObject {
      // The zero-argument constructor used by Rhino runtime to create instances
      public Functions() {
      }

      // Method jsConstructor defines the JavaScript constructor
      public void jsConstructor(Object g) {
      }

      // The class name is defined by the getClassName method
      public String getClassName() { return "Functions"; }

      // The method jsGet_count defines the count property.
      public int jsGet_ratio_value() { return 500;}
      public int jsGet_actuals_value() { return 5000;}

      // Methods can be defined using the jsFunction_ prefix. Here we define
      //  resetCount for JavaScript.
      public double jsFunction_actuals(double a, double b) {
        return a * b;
      }
      public double jsFunction_ratio(double a, double b) {
        return a / b;
      }
}

November 2, 2007

Erlang translation of States Puzzle

Filed under: Computing — admin @ 8:48 pm

I came across neat solution from A Simple Programming Puzzle Seen Through Three Different Lenses regarding states puzzle from Mark Nelson, which was originally posted NPR. Here is translation of Anders Pearson’s solution in Erlang:

 1 -module(states).

 2

 3 %%--------------------------------------------------------------------

 4 %% External exports

 5 %%--------------------------------------------------------------------

 6 -export([

 7          test/0

 8          ]).

 9

10 states() ->

11     ["alabama","alaska","arizona","arkansas","california","colorado",

12           "connecticut","delaware","florida","georgia","hawaii","idaho",

13           "illinois","indiana","iowa","kansas","kentucky","louisiana",

14           "maine","maryland","massachusetts","michigan","minnesota",

15           "mississippi","missouri","montana","nebraska","nevada",

16           "newhampshire","newjersey","newmexico","newyork","northcarolina",

17           "northdakota","ohio","oklahoma","oregon","pennsylvania","rhodeisland",

18           "southcarolina","southdakota","tennessee","texas","utah","vermont",

19           "virginia","washington","westvirginia","wisconsin","wyoming"].

20

21 test() ->

22     States = states(),

23     Dict = dict:new(),

24     {_, _, MatchingStates} = lists:foldl(fun lookup/2, {Dict, States, []}, States),

25     [["northcarolina","southdakota"],["southcarolina","northdakota"]] = MatchingStates.

26

27 lookup(State, {Dict, States, MatchingStates}) ->

28     {State, Dict1, MatchingStates1} = lists:foldl(fun lookup2/2, {State, Dict, MatchingStates}, States),

29     {Dict1, States, MatchingStates1}.

30

31 lookup2(State1, {State2, Dict, MatchingStates}) ->

32     Key = lists:sort(lists:flatten(State1 ++ State2)),

33     Response = dict:find(Key, Dict),

34     MatchingStates1 = case Response of

35         {ok, [State2, State1] } ->

36              MatchingStates;

37         {ok, _} ->

38              [[State1, State2]|MatchingStates];

39         _ ->

40             MatchingStates

41     end,

42     Dict1= dict:store(Key, [State1, State2], Dict),

43     {State2, Dict1, MatchingStates1}.




	

Powered by WordPress