在javascript中基于AND OR逻辑拆分字符串

时间:2016-04-13 18:56:55

标签: javascript string algorithm parsing data-structures

我的问题是拆分包含逻辑操作的字符串。 例如,这是我的示例字符串:

var rule = "device2.temperature > 20 || device2.humidity>68 && device3.temperature >10"

我需要以一种易于操作逻辑的方式解析该字符串,我不确定哪种方法会更好。

PS:请记住,这些规则字符串可以有10个或更多不同的条件组合,例如4个AND和6个OR。

4 个答案:

答案 0 :(得分:2)

假设没有括号,我可能会使用这样的东西(JavaScript代码):

function f(v,op,w){
  var ops = {
    '>': function(a,b){ return a > b; },
    '<': function(a,b){ return a < b; },
    '||': function(a,b){ return a || b; },
    '&&': function(a,b){ return a && b; },
     '==': function(a,b){ return a == b;}
  }

  if (ops[op]){
    return ops[op](v,w);
  } else alert('Could not recognize the operator, "' + op + '".');
}

现在,如果您可以设法获取表达式列表,则可以对它们进行系列评估:

var exps = [[6,'>',7],'||',[12,'<',22], '&&', [5,'==',5]];

var i = 0, 
    result = typeof exps[i] == 'object' ? f(exps[i][0],exps[i][1],exps[i][2]) : exps[i];

i++;

while (exps[i] !== undefined){
  var op = exps[i++],
      b = typeof exps[i] == 'object' ? f(exps[i][0],exps[i][1],exps[i][2]) : exps[i];

  result = f(result,op,b);
  i++;
}

console.log(result);

答案 1 :(得分:1)

如果您完全确定输入始终是有效的JavaScript

var rule = "device2.temperature > 20 || device2.humidity>68 && device3.temperature >10"
var rulePassed = eval(rule);

请记住,在大多数情况下,“评估”是“邪恶的”,并且有可能引入比解决的问题更多的问题。

答案 2 :(得分:1)

function parse(rule){
    return Function("ctx", "return("+rule.replace(/[a-z$_][a-z0-9$_\.]*/gi, "ctx.$&")+")");
}

比eval好一点,因为当sbd时它很可能会抛出错误。试图注入一些代码。 因为它会尝试在ctx-object而不是window-object上访问这些属性。

var rule = parse("device2.temperature > 20 || device2.humidity>68 && device3.temperature >10");
var data = {
    device2: {
        temperature: 18,
        humidity: 70
    },

    device3: {
        temperature: 15,
        humidity: 75
    }
};

console.log( rule.toString() );
console.log( rule(data) );

答案 3 :(得分:1)

矫枉过正:

要小心,未经过全面测试。可能仍然包含错误
并且,代码不会检查其语法是否有效,只会引发一些明显的错误。

var parse = (function(){    

    function parse(){
        var cache = {};

        //this may be as evil as eval, so take care how you use it.
        function raw(v){ return cache[v] || (cache[v] = Function("return " + v)) }

        //parses Strings and converts them to operator-tokens or functions
        function parseStrings(v, prop, symbol, number, string){
            if(!prop && !symbol && !number && !string){
                throw new Error("unexpected/unhandled symbol", v);
            }else{
                var w;
                switch(prop){
                    //keywords
                    case "true":
                    case "false":
                    case "null":
                        w = raw( v );
                        break;
                }
                tokens.push( 
                    w || 
                    ~unary.indexOf(prop) && v ||
                    prop && parse.fetch(v) || 
                    number && raw( number ) || 
                    string && raw( string ) ||
                    symbol
                );
            }
        }       

        var tokens = [];
        for(var i = 0; i < arguments.length; ++i){
            var arg = arguments[i];
            switch(typeof arg){
                case "number":
                case "boolean":
                    tokens.push(raw( arg ));
                    break;

                case "function":
                    tokens.push( arg );
                    break;

                case "string":
                    //abusing str.replace() as kind of a RegEx.forEach()
                    arg.replace(matchTokens, parseStrings);
                    break;
            }
        }

        for(var i = tokens.lastIndexOf("("), j; i>=0; i = tokens.lastIndexOf("(")){
            j = tokens.indexOf(")", i);
            if(j > 0){
                tokens.splice(i, j+1-i, process( tokens.slice( i+1, j ) ));
            }else{
                throw new Error("mismatching parantheses")
            }
        }
        if(tokens.indexOf(")") >= 0) throw new Error("mismatching parantheses");

        return process(tokens);
    }

    //combines tokens and functions until a single function is left
    function process(tokens){
        //unary operators like
        unary.forEach(o => {
            var i = -1;
            while((i = tokens.indexOf(o, i+1)) >= 0){
                if((o === "+" || o === "-") && typeof tokens[i-1] === "function") continue;
                tokens.splice( i, 2, parse[ unaryMapping[o] || o ]( tokens[i+1] ));
            }
        })
        //binary operators
        binary.forEach(o => {
            for(var i = tokens.lastIndexOf(o); i >= 0; i = tokens.lastIndexOf(o)){
                tokens.splice( i-1, 3, parse[ o ]( tokens[i-1], tokens[i+1] ));
            }
        })

        //ternary operator
        for(var i = tokens.lastIndexOf("?"), j; i >= 0; i = tokens.lastIndexOf("?")){
            if(tokens[i+2] === ":"){
                tokens.splice(i-1, 5, parse.ternary(tokens[i-1], tokens[i+1], tokens[i+3] ));
            }else{
                throw new Error("unexpected symbol")
            }
        }

        if(tokens.length !== 1){
            throw new Error("unparsed tokens left");
        }
        return tokens[0];
    }

    var unary = "!,~,+,-,typeof".split(",");
    var unaryMapping = {    //to avoid collisions with the binary operators
        "+": "plus",
        "-": "minus"
    }
    var binary = "**,*,/,%,+,-,<<,>>,>>>,<,<=,>,>=,==,!=,===,!==,&,^,|,&&,||".split(",");
    var matchTokens = /([a-z$_][\.a-z0-9$_]*)|([+\-*/!~^]=*|[\(\)?:]|[<>&|=]+)|(\d+(?:\.\d*)?|\.\d+)|(["](?:\\[\s\S]|[^"])+["]|['](?:\\[\s\S]|[^'])+['])|\S/gi;

    (function(){
        var def = { value: null };
        var odp = (k,v) => { def.value = v; Object.defineProperty(parse, k, def) };

        unary.forEach(o => {
            var k = unaryMapping[o] || o;
            k in parse || odp(k, Function("a", "return function(ctx){ return " + o + "(a(ctx)) }"));
        })

        //most browsers don't support this syntax yet, so I implement this manually
        odp("**", (a,b) => (ctx) => Math.pow(a(ctx), b(ctx)));
        binary.forEach(o => {
            o in parse || odp(o, Function("a,b", "return function(ctx){ return a(ctx) "+o+" b(ctx) }"));
        });

        odp("ternary", (c,t,e) => ctx => c(ctx)? t(ctx): e(ctx));

        odp("fetch", key => {
            var a = key.split(".");
            return ctx => {
                //fetches a path, like devices.2.temperature
                //does ctx["devices"][2]["temperature"];
                for(var i=0, v = ctx /*|| window*/; i<a.length; ++i){
                    if(v == null) return void 0;
                    v = v[a[i]];
                }
                return v;
            }
        });

        /* some sugar */
        var aliases = {
            "or": "||",
            "and": "&&",
            "not": "!"
        }
        for(var name in aliases) odp(name, parse[aliases[name]]);
    })();

    return parse;
})();

和你的代码:

var data = {
    device2: {
        temperature: 18,
        humidity: 70
    },

    device3: {
        temperature: 15,
        humidity: 75
    }
};

//you get back a function, that expects the context to work on (optional).
//aka. (in wich context/object is `device2` defined?)
var rule = parse("device2.temperature > 20 || device2.humidity>68 && device3.temperature >10");
console.log("your rule resolved:", rule(data));

<强>糖:

var rule1 = parse("device2.temperature > 20");
var rule2 = parse("device2.humidity>68 && device3.temperature >10");

//partials/combining rules to new ones
//only `and` (a && b), `or` (a || b), `plus` (+value), `minus` (-value) and 'not', (!value) have named aliases
var rule3 = parse.or(rule1, rule2);
//but you can access all operators like this
var rule3 = parse['||'](rule1, rule2);
//or you can combine functions and strings 
var rule3 = parse(rule1, "||", rule2);

console.log( "(", rule1(data), "||", rule2(data), ") =", rule3(data) );

//ternary operator and Strings (' and " supported)
var example = parse(rule1, "? 'device2: ' + device2.temperature : 'device3: ' + device3.temperature");
console.log( example(data) )

还有什么要知道的:

代码处理运算符优先级并支持圆括号

如果无法获取Path,则特定函数返回undefined(此处不抛出错误)
访问路径中的数组键:parse("devices.2.temperature")获取devices[2].temperature

未实施:

解析数组并解析函数调用以及值修改的所有内容。这个引擎做了一些计算,它期望一些Value in,并给你一个值。不多也不少。