Details

Type: Improvement

Status: Open

Priority: Major

Resolution: Unresolved

Affects Version/s: 2.0

Fix Version/s: None

Component/s: Data Binder, Framework, Model Transformation System

Labels:None
Description
Our "avoidance of syntax" in the framework makes it amenable to tools but hard to author and read by humans in many scenarios. Currently all of the basic mathematical operators, as they arise in transforms and relay rules, are expressed by full JSON blocks and so transform rules such as the following are very bulky:
"speech\\.espeak\\.rate": { "transform": { "type": "fluid.transforms.round", "input": { "transform": { "type": "fluid.transforms.binaryOp", "right": 3.10, "operator": "/", "left": { "transform": { "type": "fluid.transforms.binaryOp", "right": 80, "operator": "", "left": { "transform": { "type": "fluid.transforms.condition", "truePath": "http://registry\\.gpii\\.net/common/speechRate", "false": { "transform": { "type": "fluid.transforms.binaryOp", "leftPath": "http://registry\\.gpii\\.net/common/speechRate", "operator": "/", "right": 3 } }, "condition": { "transform": { "type": "fluid.transforms.binaryOp", "leftPath": "http://registry\\.gpii\\.net/common/speechRate", "operator": "<=", "right": 390 } } } } } } } } } },
This should be expressed as
Math.round(3.1 / (80  (speechRate <= 390 ? speechRate : speechRate / 3)))
using standard mathematical syntax as operated in JavaScript together with some ability to make a local definition for speechRate.
We have avoided such syntax so far since we really have a need to control it rather than adopt some sleazy solution (of the sort commonly operated by those incapable or unwilling to write parsers) of simply slinging some perhaps partially sanitised expression into JavaScript's "eval" function.
The time has come to write a little parser of our own. Some early discussion of this is available in the wiki page https://wiki.fluidproject.org/display/fluid/Notes+on+Expressionism+in+Model+Relay . This speculates that we might use the "esprima" parser but really this is an incredibly bulky thing that we cannot afford to deliver wherever we deliver the framework. Instead, we should handroll a simple parser based on the straightforward and widelyattested Shunting Yard Algorithm.
The parser should recognise two, nested syntaxes. The first, for "full expressions" should recognise any expression using the standard mathematical unary, binary and ternary operators in JavaScript (where these do not have any sideeffects) as well as any publically addressible functions such as Math.round, Math.pow etc. These will become packaged as a standard full model transform rule named "fluid.transforms.expression".
The framework will for the foreseeable future make no attempt to compute inverses of these expressions  they can be contributed by explicitly writing the inverse transform (if it exists) on a reverse leg.
The second syntax, for "reduced expressions" or "linear expressions" will recognise any expression which is linear  that is, can be recognised to be of the form a * x + b where a and b are constants. There are some extensions that we desire here with respect to the strict syntax written there. Firstly, we would like to treat the unary minus  and logical negation operators ! as incorporated into the possibility for the multiplier a  e.g. !x is a linear expression, as well as allowing for the multiplication by a to instead take the syntactic form of division. Secondly, we wish to support "compound constants" which are composed of primitive constants by means of arbitrary mathematical expressions as seen in the full parser. For example, x / (60 * 60 * 24)  7 * 4 should also be recognised as a linear expression (in practice "compiled" as 0.00001157407 * x  28  this form should make it clear why we want to recognise compound constants!).
The system will support linear expressions anywhere where an IoCmediated expression is accepted  for example in a model relay rule or a model listener rule. That is, it should be possible to write:
model: {
negatedValue: "!{otherComponent}.model.value"
}
The framework will automatically compute inverses of any linear expressions and apply them on any reverse linkage. It will reject any linear expression for which the coefficient of x evaluates to zero.