Typescript Math Toolkit Expression Engine
The TSMT expression engine allows general expressions to be parsed and then evaluated with either numeric, string, and/or boolean values for the independent variables. The expression returns either a number, string, or boolean (although the latter is by far the most common).
My initial use case for an expression engine was a lightweight rules engine. Expressions may also be used to dynamically evaluate what parts of a UI to show, for example, based on either formulas returned from a server (and independent variables from a data object) or those computed on-the-fly as a user interacts with an application.
This version of the engine was created by extending the function parser code base. My current belief is that a refactor is in order before a formal release to the toolkit. The current implementation, however, is useful as I have already built a lightweight rules engine on top of a modified version of this code for a client.
And, just for completeness, I will mention that there really isn't anything new under the sun; the fundamentals of how this and similar codes work are described here.
Author: Jim Armstrong - The Algorithmist
theAlgorithmist [at] gmail [dot] com
Installation involves all the usual suspects
- npm and gulp installed globally
- Clone the repository
- npm install
- get coffee (this is the most important step)
Building and running the tests
The test suite is in Mocha/Chai and specs reside in the test folder.
Consider a very simple expression, x + 1. This could be interpreted as a math formula and evaluated for numerical values of the independent variable, x. So, the expression for an input, x = 1, evaluates to a value of 2.0. But, what if x is a string? We would expect a value of x = 'abc' to result in an evaluation of 'abc1'.
Now, what about x + 1 < y where both x and y are independent variables that could take on either string and/or numeric values? The result would depend on the input type as coercion to string invokes a lexicographic comparison vs. a simpler numeric comparison for inputs of type number.
The first principle of the TSMT Expression Engine is that independent variables and the output of an expression may be a number, string, or boolean value. The engine attempts to evaluate the expression to a reasonable result based in input type(s).
The following numeric and boolean operations are supported in the engine: +, -, *, /, <=, >=, <, >, and =
The following math functions are supported: abs, ceil, floor, max, min, round
It is possible to check if a string is contained in a list (array) of strings, i.e.
"x ~ [ab, cd, efg]" or "x ~ [1, 2, 3]"
The independent variable, x should be a string and the list of strings to check against should be inside brackets and without quotes. This expression evaluates to a boolean.
It is possible to check against a string literal. Currently, the literal should be to the right of a comparison operator, i.e. "x = 'abc'". This checks string values of the independent variable, x agains the literal, 'abc' and returns a boolean.
There is currently no enforcement of order of operations, so make liberal use of parentheses to make expressions unambiguous, especially when using combinations of numeric and boolean operations. For example, "(2*x) < (3*y - 2)". Refer to the specs in the test folder for more examples.
In applications, I tend to enforce some order of operations when building expressions, since it is easier to insert proper parentheses at that time. This requirement may be relaxed in a future release.
The TSMT$ExpressionEngine class contains the following public methods.
set variables(vars: Array<string>) clear(): void parse(str: string): boolean get stack(): Array<string> eval(variableList: Array<string>, variables: Array<expressionOperand>, stack: Array<string>): expressionValue evaluate(variables: Array<expressionOperand>): expressionValue
Typical usage of the TSMT$ExpressionEngine class involves:
1 - Define the list of independent variable, i.e. ['X', 'COLOR', 'LIMIT']
2 - Input an expression involving these variables into the parser (check the return for success)
3 - Evaluate the expression for a list of actual values for each independent variable (in the order defined)
The list of independent variables may be provided at construction or input later with a mutator.
Always call the clear() method before parsing a new expression.
It is possible to access a copy of the expression stack after parsing and then evaluate an expression using the saved stack at a later point in an application. I have personally found this feature very useful in rule engines.
Evaluate the expression, "x + y" where both x and y are string variables.
const expression: TSMT$ExpressionEngine = new TSMT$ExpressionEngine() expression.variables = ["x", "y"]; let success: boolean = expression.parse( "x + y" ); expect(success).to.be.true; let value: expressionValue = expression.evaluate(['a', 'b']); expect(<string> value).to.equal('ab'); value = expression.evaluate(['DFW', 'SFO']) expect(<string> value).to.equal('DFWSFO');
Evaluate the expression, "(2*x + 1) - (3*y - 2)" after having evaluated the expression in the above example. x and y are number variables.
expression.clear(); expression.variables = ["x", "y"]; success = expression.parse( "(2*x + 1) - (3*y - 2)" ); expect(success).to.be.true; value = expression.evaluate([0, 0]); expect(<number> value).to.equal(3); value = expression.evaluate([1, 1]) expect(<number> value).to.equal(2); value = expression.evaluate([-2, 3]) expect(<number> value).to.equal(-10);
Refer to the specs in the test folder for more usage examples.
Free Software? Yeah, Homey plays that