diff --git a/README.md b/README.md index 28ce480..33d03af 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Values | Description --- | --- 43, -1.234 | Numbers "hello" | String -foo, a.b.c | External data variable defined by application (may be numbers or strings) +foo, a.b.c, 'foo-bar' | External data variable defined by application (may be numbers or strings) Numeric arithmetic | Description --- | --- @@ -86,6 +86,7 @@ x < y | Less than x <= y | Less than or equal to x > y | Greater than x >= y | Greater than or equal to +x ~= y | Regular expression match x in (a, b, c) | Equivalent to (x == a or x == b or x == c) x not in (a, b, c) | Equivalent to (x != a and x != b and x != c) diff --git a/filtrex.js b/filtrex.js index 6b47d93..8846331 100644 --- a/filtrex.js +++ b/filtrex.js @@ -89,6 +89,7 @@ function filtrexParser() { ['\\,', 'return ",";'], ['==', 'return "==";'], ['\\!=', 'return "!=";'], + ['\\~=', 'return "~=";'], ['>=', 'return ">=";'], ['<=', 'return "<=";'], ['<', 'return "<";'], @@ -103,6 +104,7 @@ function filtrexParser() { ['\\s+', ''], // skip whitespace ['[0-9]+(?:\\.[0-9]+)?\\b', 'return "NUMBER";'], // 212.321 ['[a-zA-Z][\\.a-zA-Z0-9_]*', 'return "SYMBOL";'], // some.Symbol22 + ['\'(?:[^\'])*\'', 'yytext = yytext.substr(1, yyleng-2); return "SYMBOL";'], // 'some-symbol' ['"(?:[^"])*"', 'yytext = yytext.substr(1, yyleng-2); return "STRING";'], // "foo" // End @@ -119,7 +121,7 @@ function filtrexParser() { ['left', 'or'], ['left', 'and'], ['left', 'in'], - ['left', '==', '!='], + ['left', '==', '!=', '~='], ['left', '<', '<=', '>', '>='], ['left', '+', '-'], ['left', '*', '/', '%'], @@ -145,6 +147,7 @@ function filtrexParser() { ['not e' , code(['Number(!', 2, ')'])], ['e == e' , code(['Number(', 1, '==', 3, ')'])], ['e != e' , code(['Number(', 1, '!=', 3, ')'])], + ['e ~= e' , code(['RegExp(', 3, ').test(', 1, ')'])], ['e < e' , code(['Number(', 1, '<' , 3, ')'])], ['e <= e' , code(['Number(', 1, '<=', 3, ')'])], ['e > e' , code(['Number(', 1, '> ', 3, ')'])], diff --git a/src/filtrex.js b/src/filtrex.js index b8acb6f..927fdc1 100644 --- a/src/filtrex.js +++ b/src/filtrex.js @@ -46,9 +46,12 @@ function compileExpression(expression, extraFunctions /* optional */) { tree.forEach(toJs); js.push(';'); - var func = new Function('functions', 'data', js.join('')); + function unknown(funcName) { + throw 'Unknown function: ' + funcName + '()'; + } + var func = new Function('functions', 'data', 'unknown', js.join('')); return function(data) { - return func(functions, data); + return func(functions, data, unknown); }; } @@ -86,6 +89,7 @@ function filtrexParser() { ['\\,', 'return ",";'], ['==', 'return "==";'], ['\\!=', 'return "!=";'], + ['\\~=', 'return "~=";'], ['>=', 'return ">=";'], ['<=', 'return "<=";'], ['<', 'return "<";'], @@ -100,6 +104,7 @@ function filtrexParser() { ['\\s+', ''], // skip whitespace ['[0-9]+(?:\\.[0-9]+)?\\b', 'return "NUMBER";'], // 212.321 ['[a-zA-Z][\\.a-zA-Z0-9_]*', 'return "SYMBOL";'], // some.Symbol22 + ['\'(?:[^\'])*\'', 'yytext = yytext.substr(1, yyleng-2); return "SYMBOL";'], // 'some-symbol' ['"(?:[^"])*"', 'yytext = yytext.substr(1, yyleng-2); return "STRING";'], // "foo" // End @@ -116,7 +121,7 @@ function filtrexParser() { ['left', 'or'], ['left', 'and'], ['left', 'in'], - ['left', '==', '!='], + ['left', '==', '!=', '~='], ['left', '<', '<=', '>', '>='], ['left', '+', '-'], ['left', '*', '/', '%'], @@ -142,6 +147,7 @@ function filtrexParser() { ['not e' , code(['Number(!', 2, ')'])], ['e == e' , code(['Number(', 1, '==', 3, ')'])], ['e != e' , code(['Number(', 1, '!=', 3, ')'])], + ['e ~= e' , code(['RegExp(', 3, ').test(', 1, ')'])], ['e < e' , code(['Number(', 1, '<' , 3, ')'])], ['e <= e' , code(['Number(', 1, '<=', 3, ')'])], ['e > e' , code(['Number(', 1, '> ', 3, ')'])], @@ -151,7 +157,7 @@ function filtrexParser() { ['NUMBER' , code([1])], ['STRING' , code(['"', 1, '"'])], ['SYMBOL' , code(['data["', 1, '"]'])], - ['SYMBOL ( argsList )', code(['functions.', 1, '(', 3, ')'])], + ['SYMBOL ( argsList )', code(['(functions.hasOwnProperty("', 1, '") ? functions.', 1, '(', 3, ') : unknown("', 1, '"))'])], ['e in ( inSet )', code([1, ' in (function(o) { ', 4, 'return o; })({})'])], ['e not in ( inSet )', code(['!(', 1, ' in (function(o) { ', 5, 'return o; })({}))'])], ], diff --git a/test/filtrex-test.html b/test/filtrex-test.html index a73766f..eb1151d 100644 --- a/test/filtrex-test.html +++ b/test/filtrex-test.html @@ -104,6 +104,11 @@ eq(1, compileExpression('foo not in ("aa", "bb")')({foo:'cc'})); }, + 'regexp test': function() { + eq(1, compileExpression('foo ~= "^[hH]ello"')({foo:'hello'})); + eq(0, compileExpression('foo ~= "^[hH]ello"')({foo:'bye'})); + }, + 'a ? b : c': function() { eq(4, compileExpression('1 > 2 ? 3 : 4')()); eq(3, compileExpression('1 < 2 ? 3 : 4')()); @@ -120,6 +125,11 @@ eq(123, compileExpression('order.gooandstuff')({'order.gooandstuff': 123})); }, + 'quoted symbols': function() { + eq(123, compileExpression('\'hello-world-foo\'')({'hello-world-foo': 123})); + eq(123, compileExpression('\'order+goo*and#stuff\'')({'order+goo*and#stuff': 123})); + }, + 'custom functions': function() { function triple(x) { return x * 3; }; eq(21, compileExpression('triple(v)', {triple:triple})({v:7}));