Skip to content

About text-expression evaluation #8

@masbicudo

Description

@masbicudo

Some time ago, we talked about text expressions... and I have implemented it generating a function that was able to be called passing an object. Since that comment, I have evolved it a little.

Example 1: createEvaluator does not depend on an existing instance of the object to create the evaluator function:

var fn = createEvaluator("x+y+z");
var result = fn({x: 1, y: 2, z: 3});
// result now contains 6

Example 2: it allows you to use variable names in the code, without requiring them to be set on the source object:

var fn = createEvaluator("x+(y||0)+(z||0)");
var result = fn({x: 1});
// result now contains 1

I haven't tested it widely though... but it works in current versions of FF and Chrome.

Take a look at tell me what you do think.

The code:

function rndStr() {
    return Math.floor((1 + Math.random()) * 0x100000000).toString(16).substring(1);
}

function escapeRegExp(str) {
    return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}

function getVarNameExtractor() {
    var errMsg = null;
    var rgx = null;
    for (var tryes = 0; tryes < 10; tryes++) {
        var n = rndStr() + rndStr() + rndStr() + rndStr();
        n = "__Invalid_Reference__" + n + "__";
        try {
            var func = Function.apply(
                null,
                ["'use strict'\nreturn "+n+";"]);
            func();
        }
        catch (e) {
            errMsg = e.message;
            var escMsg = escapeRegExp(errMsg);
            var rgxStr = escMsg.replace(new RegExp(escapeRegExp(n),'g'), "(.*)");
            if (rgxStr !== escMsg) {
                var rgx = new RegExp(rgxStr, 'g');;
                return function(error) {
                    rgx.lastIndex = 0;
                    var match = rgx.exec(error.message);
                    return (match && match[1]) || null;
                }
            }
        }
    }

    return null;
}

function getVarNames(expr, model) {
    var vne = getVarNameExtractor();
    var names = (model && Object.keys(model)) || [];
    if (vne) {
        while (true) {
            var func = Function.apply(
                null,
                names.concat(["'use strict';\nreturn ("+expr+");"]));

            try {
                func(model);
                break;
            }
            catch (e) {
                var missedName = vne(e);
                if (missedName)
                    names.push(missedName);
                else
                    break;
            }
        }
    }

    return names;
}

function createEvaluator(expr, model) {
    var names = getVarNames(expr, model);

    var func = Function.apply(
        null,
        names.concat(["'use strict';\nreturn ("+expr+");"]));

    var func2 = Function.apply(
        null,
        [
            "func",
            "'use strict';\nreturn function(o) {"+
                "return func("+names.map(function(x){return "o."+x;}).join(",")+");"+
            "};"
        ]);

    return func2(func);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions