The Scala Expression Engine evolved from a scratch project
which I had used to get familiar with Scala.
This still shines through in various places.
Not only is the source code poorly documented,
I also don't care for any size or performance issues.
So if you are looking for a solution that is both compact and fast,
you should probably better take a look at other projects like
JEP or JEPLite, which both work fine.
Scala turned out to be extremely well suited to write things like this,
especially due to its embedded parser library.
Therefore the project grew fast into something that became more and more powerful.
In the meantime, it has become a language of its own,
called "Sel" for Scala expression language.
Although it doesn't look much like Scala at first glance,
some language constructs reflect the Scala philosophy of handling things.
So, I thought other people might find it useful, too (or at least interesting ;-) ).
Colomam, 2012
See see = See.create();The context provides the default parser and evaluator as well as an empty binding. In most cases one context will be sufficient for the whole application that uses it.
long result = see.evalAsLong("2 ** 16");.See will take a best-effort approach to convert the evaluation result into the desired result type. If that doesn't work, some kind of
see.SeeException
will be thrown. The same goes for parsing
or evaluation problems.
INode node = see.parse("2 ** 16"); long result = see.evalAsLong(node);The node may be evaluated as often as necessary, even with different result types, e.g.
try { double result = see.evalAsDouble(node); // do something with result } catch (SeeException ex) { // didn't work, may be it was a string instead? String s = see.evalAsString(node); }If an expression refers to a variable, results will reflect changes of that variable after re-evaluation:
INode n = see.parse("5 * x"); see.set("x", 1); assert(see.evalAsLong(n) == 5L); see.set("x", 2); assert(see.evalAsLong(n) == 10L);
Although I did not run exhaustive multithreading tests, some rules may be derived from design:
x = f(x)
generates a race condition.
In such cases some kind of external synchronization is required.
Synchronization on the context is typically sufficient.
INode n = see.parse("x = x + 1"); see.set("x", 1); synchronized(see) { long next = see.evalAsLong(n); }If an application can guarantee that the context is never modified (e.g. by use of the alternative parser), synchronization may be omitted.
a + b * c/d - 5Note that the exponential operator is **, not ^. The latter will perform a bitwise xor!
x = 5; y = 6
vx = (1, 2, 1.0, true)
x = vx@0; x = vx@-1; vx@@(1, 3) == (2, 1.0); vx@@(3, 100) == (true)Slicing should always succeed. If indices are out of bounds, the result will be an empty vector.
"x = " + 5 == "x = 5"; "v = " + (1, 2) == "v = (1, 2)"Conversions to string are implicit as shown above, but conversions from string to other types must be explicitly stated to avoid unexpected propagations.
s = "10"; 20 + s; // fails to evaluate 30 = 20 + int(s); // fine
_x = 5; y = 5; {_x = 6; y = 10}; _x + yyields 15, not 10 or 16, because _x is only visible within the inner scope while y is changed in the outer scope.
f(x,y,z) := {x + y + z}
f(x) := {2 * x}; vf = f; 8 == vf(4); 5.0 == vf(2.5)This allows some form of currying like this:
f(x) := {x * 2}; g(x) := {x * 3}; curry(c) := {r(fv) := {fv(c)} r }; f3 = curry(3); f4 = curry(4); 6 == f3(f); 12 == f4(g); curry(3)(g);
cond ? iftrue : iffalseThe loop operator looks quite similar:
while ?? body : otherwiseNote that it returns a value! Either that of last executed
body
statement or otherwise
,
if the while
-condition was never true.
The body
-statement must of course modify the value
returned by the while
-condition or you will get an infinite loop.y = 1; x > 0 ?? x -= 1; y *= 2 : x < 0 ? -1 : 0After evaluating this expression, y will hold 2**x, if x was greater than 0 before. If x was below zero, y will be set to -1, otherwise to 0.
java -jar see.jar
java -jar see.jar --help