gearz-lab / react-ui

Bootstrap based data components for React
http://reactui.com
MIT License
13 stars 1 forks source link

About text-expression evaluation #8

Open masbicudo opened 9 years ago

masbicudo commented 9 years ago

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);
}
andrerpena commented 9 years ago

I'm sorry I didn't remember that. I didn't mean to reimplement that.

Is your version any better than the one I submitted with the unit tests? The only difference I'm seeing is that yours support an evaluator function to be returned. Mine just evaluates it.

If your version is any better we can update the codebase.

masbicudo commented 9 years ago

There are some differences:

  1. it creates a function... that could be used to replace the text expression once it is found, so once converted to a function you can reuse it
  2. it supports the use of variables that are not in the object, so if you happen to find a partially filled object, it still works, variable will be left undefined
  3. more complex, so it is more difficult to understand... I have commented the code and commited it to my Web-Scripts repository
  4. I didn't test it in IE, in any mobile browsers, nor in old chrome or FF, or any other browser (i.e. Opera, Safari)... just the latest Chrome and FF versions were tested (using the Console)... I don't have tests for it right now

It is saved in my repo, so we can choose to use it or not at any moment, if you think it is not the right time.

andrerpena commented 9 years ago

Yes, I think we should come back to this once it's proven to be a problem. If we stick to every nitty gritty details we won't go any further. I'm currently focused on converting a sample component to CJS and create a build process for ReactComponents.