Source: form-script/evaluator.js

import { Parser } from './parser';

/**
 * Main entry point of form-script
 * @module form-script
 */

const OPERATORS = {
    //Binary ops
    "+": function(left, right) {
        return left + right;
    },
    "-": function(left, right) {
        return left - right;
    },
    "*": function(left, right) {
        return left * right;
    },
    "/": function(left, right) {
        return left / right;
    },
    "%": function(left, right) {
        return left % right;
    },
    "<=": function(left, right) {
        return left <= right;
    },
    ">=": function(left, right) {
        return left >= right;
    },
    "<": function(left, right) {
        return left < right;
    },
    ">": function(left, right) {
        return left > right;
    },
    "==": function(left, right) {
        return left == right;
    },
    "!=": function(left, right) {
        return left != right;
    },
    "and": function(left, right) {
        return left && right;
    },
    "or": function(left, right) {
        return left || right;
    },
    "^": function(left, right) {
        return left ** right;
    },
    //Unary ops
    "u+": function(val) {
        return val;
    },
    "u-": function(val) {
        return -val;
    },
    "!": function(val) {
        return !val;
    }
}

/**
 * Creates a new Evaluator which can be used to evaluate FormScript code.
 * @class
 */
function Evaluator(context) {
    const dom = context;
    let ast;

    var evalExpression = function(expression) {
        let valueStack = [];

        for (let i = 0; i < expression.outputQueue.length; i++) {
            var tok = expression.outputQueue[i];
        
            if (tok.type === "NUMBER" || tok.type === "STRING") {
                valueStack.push(tok.val);
            }

            if (tok.type === "FUNCALL") {
                let funVal = evalFunctionCall(tok);
                valueStack.push(funVal);
            }

            if (tok.type === "VARIABLE_NAME") {
                valueStack.push(extractValue(tok.val));
            }
        
            if (tok.isOperator) {
                var sum;

                if (tok.isUnary) {
                    var val = valueStack.pop();
                    sum = OPERATORS[tok.val](val);
                } else {
                    var right = valueStack.pop();
                    var left = valueStack.pop();
                    sum = OPERATORS[tok.val](left, right);
                }

                valueStack.push(sum);
            }
        }
        
        return valueStack.pop();
    }

    var extractValue = function(variable) {
        var xElem = dom.getXElemByScriptAlias(variable);
        var val = xElem.getValue();
        var numericVal = parseFloat(val);
    
        if (isNaN(numericVal)) {
            return val;
        } else {
            return numericVal;
        }
    }

    var evalAssignment = function(assignment) {
        var xElem = dom.getXElemByScriptAlias(assignment.lhs);
        var val = evalExpression(assignment.rhs);
        xElem.setValue(val);
    }

    var evalFunctionCall = function(functionCall) {
        var xElem = dom.getXElemByScriptAlias(functionCall.variableName);
        var funName = functionCall.funName;
        var args = [];

        for (let i = 0; i < functionCall.args.length; i++) {
            let arg = functionCall.args[i];
            let evaluatedArg = evalExpression(arg);
            args.push(evaluatedArg);
        }

        return xElem[funName](...args);
    }

    var evalIfThen = function(ifThen) {
        let condition = evalExpression(ifThen.condition);

        if (condition) {
            for (let i = 0; i < ifThen.true.length; i++) {
                let trueAction = ifThen.true[i];
                
                if (trueAction.type === "FUNCALL") {
                    evalFunctionCall(trueAction);
                } else if (trueAction.type === "ASSIGNMENT") {
                    evalAssignment(trueAction);
                } else {
                    throw "Invalid action: " + trueAction.type;
                }
            }
        } else {
            for (let i = 0; i < ifThen.false.length; i++) {
                var falseAction = ifThen.false[i];
                evalFunctionCall(falseAction);
            }
        }
    }

    /**
     * The main method to evaluate FormScript code
     * @method
     * @param {string} script - FormScript source code
     * @param {string} context - The context in which FormScript code is evaluated. Values: "builder", "viewer"
     */
    this.eval = function(script, context) {
        let parser;

        if (context === "builder" || !ast) {
            parser = new Parser(script);
            ast = parser.parse();
        }

        for (let i = 0; i < ast.length; i++) {
            let stmt = ast[i];

            if (stmt.type === "IFTHEN") {
                evalIfThen(stmt);
            } else if (stmt.type === "FUNCALL") {
                evalFunctionCall(stmt);
            } else if (stmt.type === "ASSIGNMENT") {
                evalAssignment(stmt);
            }
        }
    }

    /**
     * Binds the Evaluator to the form.
     * @method
     * @param {object} params.widget - The XReport widget to bind the Evaluator to.
     * @param {string} params.context - The context in which FormScript code is evaluated. Values: "builder", "viewer"
     */
    this.bind = function(params) {
        let that = this;
        let widget = params.widget;
        let form = widget.getForm();

        if (params.context === "builder") {
            form.on("change", function() {
                let start = performance.now();
                that.eval(widget.getScript(), params.context);
                console.log(performance.now() - start);
            });
        } else {
            form.on("change", function() {
                that.eval(dom.getScript(), params.context);
            });
        }
        //Initial trigger to go to "init state"
        widget.getForm().trigger("change");
      }
}

export { Evaluator };