From 369a37512844c8741fdcf55085fb64d2abcc8832 Mon Sep 17 00:00:00 2001 From: m93a Date: Tue, 21 Sep 2021 13:33:47 +0200 Subject: [PATCH] recompilled + minor improvements --- dist/browser/filtrex.js | 222 ++++++++++++++++++++++++++++++------ dist/browser/filtrex.min.js | 2 +- dist/cjs/filtrex.js | 222 ++++++++++++++++++++++++++++++------ dist/esm/errors.mjs | 95 +++++++++++++++ dist/esm/filtrex.d.ts | 14 ++- dist/esm/filtrex.mjs | 105 +++++++++++++---- dist/esm/generateParser.mjs | 12 +- dist/esm/parser.mjs | 12 +- dist/esm/utils.mjs | 12 +- src/filtrex.d.ts | 4 +- src/filtrex.mjs | 54 ++++++++- test/arithmetics.js | 2 +- 12 files changed, 643 insertions(+), 113 deletions(-) create mode 100644 dist/esm/errors.mjs diff --git a/dist/browser/filtrex.js b/dist/browser/filtrex.js index f04c8cf..99f23a6 100644 --- a/dist/browser/filtrex.js +++ b/dist/browser/filtrex.js @@ -1662,13 +1662,19 @@ var filtrex = (function (exports) { case 26: return "Number"; case 27: - yy_.yytext = JSON.stringify(yy_.yytext); + yy_.yytext = JSON.stringify({ + name: yy_.yytext, + type: 'unescaped' + }); return "Symbol"; case 28: - yy_.yytext = yy.buildString("'", yy_.yytext); + yy_.yytext = JSON.stringify({ + name: yy.buildString("'", yy_.yytext), + type: 'single-quoted' + }); return "Symbol"; case 29: - yy_.yytext = yy.buildString('"', yy_.yytext); + yy_.yytext = JSON.stringify(yy.buildString('"', yy_.yytext)); return "String"; case 30: return "EOF"; @@ -1695,6 +1701,102 @@ var filtrex = (function (exports) { const parser = _parser; _parser.Parser; + /** + * Runtime error – user attempted to call a function + * which is not a predefined function, nor specified + * in `options.extraFunctions`. + * + * @prop {string} functionName + * @prop {string} I18N_STRING has the value `'UNKNOWN_FUNCTION'` + */ + class UnknownFunctionError + extends ReferenceError { + I18N_STRING = 'UNKNOWN_FUNCTION' + + constructor (funcName) { + super(`Unknown function: ${funcName}()`); + this.functionName = funcName; + } + } + + /** + * Runtime error – user attempted to access a property which + * is not present in the `data` object, nor in the `constants`. + * If the property is meant to be empty, use `undefined` or + * `null` as its value. If you need to use optional properties + * in your `data`, define a `customProp` function. + * + * @prop {string} propertyName + * @prop {string} I18N_STRING has the value `'UNKNOWN_PROPERTY'` + */ + class UnknownPropertyError + extends ReferenceError { + I18N_STRING = 'UNKNOWN_PROPERTY' + + constructor (propName) { + super(`Property “${propName}” does not exist.`); + this.propertyName = propName; + } + } + + /** + * Compile time error – you specified an option which + * was not recognized by Filtrex. Double-check your + * spelling and the version of Filtrex you are using. + * + * @prop {string} keyName + * @prop {string} I18N_STRING has the value `'UNKNOWN_OPTION'` + */ + class UnknownOptionError + extends TypeError { + I18N_STRING = 'UNKNOWN_OPTION' + + constructor (key) { + super(`Unknown option: ${key}`); + this.keyName = key; + } + } + + /** + * Runtime error – user passed a different type than the one + * accepted by the function or operator. + * + * The possible values of `expectedType` and `recievedType` + * are: `"undefined"`, `"null"`, `"true"`, `"false"`, `"number"`, + * `"text"`, `"unknown type"`, `"list"`, `"object"`, `"text or number"` + * and `"logical value (“true” or “false”)"` + * + * @prop {string} expectedType + * @prop {string} recievedType + * @prop {string} I18N_STRING has the value `'UNEXPECTED_TYPE'` + */ + class UnexpectedTypeError + extends TypeError { + I18N_STRING = 'UNEXPECTED_TYPE' + + constructor (expected, got) { + super(`Expected a ${expected}, but got a ${got} instead.`); + + this.expectedType = expected; + this.recievedType = got; + } + } + + /** + * An internal error. This was not meant to happen, please report + * at https://github.com/m93a/filtrex/ + * + * @prop {string} I18N_STRING has the value `'INTERNAL'` + */ + class InternalError + extends Error { + I18N_STRING = 'INTERNAL' + + constructor (message) { + super(message); + } + } + /** * Determines whether an object has a property with the specified name. * @param {object} obj the object to be checked @@ -1774,33 +1876,33 @@ var filtrex = (function (exports) { value = unwrap(value); if (typeof value === 'number') return value - throw new TypeError(`Expected a number, but got a ${prettyType(value)} instead.`) + throw new UnexpectedTypeError('number', prettyType(value)) } function str(value) { value = unwrap(value); if (typeof value === 'string') return value - throw new TypeError(`Expected a text, but got a ${prettyType(value)} instead.`) + throw new UnexpectedTypeError('text', prettyType(value)) } function numstr(value) { value = unwrap(value); if (typeof value === 'string' || typeof value === 'number') return value - throw new TypeError(`Expected a text or a number, but got a ${prettyType(value)} instead.`) + throw new UnexpectedTypeError('text or number', prettyType(value)) } function bool(value) { value = unwrap(value); if (typeof value === 'boolean') return value - throw new TypeError(`Expected a logical value (“true” or “false”), but got a ${prettyType(value)} instead.`) + throw new UnexpectedTypeError('logical value (“true” or “false”)', prettyType(value)) } function arr(value) { if (value === undefined || value === null) { - throw new TypeError(`Expected a list, but got ${value} instead.`) + throw new UnexpectedTypeError('list', prettyType(value)) } if (Array.isArray(value)) { @@ -1841,7 +1943,7 @@ var filtrex = (function (exports) { }, unknown(funcName) { - throw new ReferenceError('Unknown function: ' + funcName + '()') + throw new UnknownFunctionError(funcName) }, coerceArray: arr, @@ -1862,22 +1964,22 @@ var filtrex = (function (exports) { let built = ''; if (literal[0] !== quote || literal[literal.length-1] !== quote) - throw new Error(`Unexpected internal error: String literal doesn't begin/end with the right quotation mark.`) + throw new InternalError(`Unexpected internal error: String literal doesn't begin/end with the right quotation mark.`) for (let i = 1; i < literal.length - 1; i++) { if (literal[i] === "\\") { i++; - if (i >= literal.length - 1) throw new Error(`Unexpected internal error: Unescaped backslash at the end of string literal.`) + if (i >= literal.length - 1) throw new InternalError(`Unexpected internal error: Unescaped backslash at the end of string literal.`) if (literal[i] === "\\") built += '\\'; else if (literal[i] === quote) built += quote; - else throw new Error(`Unexpected internal error: Invalid escaped character in string literal: ${literal[i]}`) + else throw new InternalError(`Unexpected internal error: Invalid escaped character in string literal: ${literal[i]}`) } else if (literal[i] === quote) { - throw new Error(`Unexpected internal error: String literal contains unescaped quotation mark.`) + throw new InternalError(`Unexpected internal error: String literal contains unescaped quotation mark.`) } else { @@ -1885,7 +1987,7 @@ var filtrex = (function (exports) { } } - return JSON.stringify(built) + return built }, reduceRelation(arr) { @@ -1912,15 +2014,59 @@ var filtrex = (function (exports) { parser.yy = Object.create(std); + + /** - * Filtrex provides compileExpression() to compile user expressions to JavaScript. + * A simple, safe, JavaScript expression engine, allowing end-users to enter arbitrary expressions without p0wning you. + * + * @example + * // Input from user (e.g. search filter) + * let expression = 'transactions <= 5 and abs(profit) > 20.5'; * - * See https://github.com/joewalnes/filtrex for tutorial, reference and examples. - * MIT License. + * // Compile expression to executable function + * let myfilter = compileExpression(expression); * - * Includes Jison by Zachary Carter. See http://jison.org/ + * // Execute function + * myfilter({transactions: 3, profit:-40.5}); // returns 1 + * myfilter({transactions: 3, profit:-14.5}); // returns 0 * - * -Joe Walnes + * @param expression + * The expression to be parsed. Under the hood, the expression gets compiled to a clean and fast JavaScript function. + * There are only 2 types: numbers and strings. Numbers may be floating point or integers. Boolean logic is applied + * on the truthy value of values (e.g. any non-zero number is true, any non-empty string is true, otherwise false). + * Examples of numbers: `43`, `-1.234`; example of a string: `"hello"`; example of external data variable: `foo`, `a.b.c`, + * `'foo-bar'`. + * You can use the following operations: + * * `x + y` Add + * * `x - y` Subtract + * * `x * y` Multiply + * * `x / y` Divide + * * `x % y` Modulo + * * `x ^ y` Power + * * `x == y` Equals + * * `x < y` Less than + * * `x <= y` Less than or equal to + * * `x > y` Greater than + * * `x >= y` Greater than or equal to + * * `x == y <= z` Chained relation, equivalent to `(x == y and y <= z)` + * * `x of y` Get property x of object y + * * `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)` + * * `x or y` Boolean or + * * `x and y` Boolean and + * * `not x` Boolean not + * * `if x then y else z` If boolean x, value y, else z + * * `( x )` Explicity operator precedence + * * `( x, y, z )` Array of elements x, y and z + * * `abs(x)` Absolute value + * * `ceil(x)` Round floating point up + * * `floor(x)` Round floating point down + * * `log(x)` Natural logarithm + * * `max(a, b, c...)` Max value (variable length of args) + * * `min(a, b, c...)` Min value (variable length of args) + * * `round(x)` Round floating point + * * `sqrt(x)` Square root + * * `myFooBarFunction(x)` Custom function defined in `options.extraFunctions` */ function compileExpression(expression, options) { @@ -1929,12 +2075,12 @@ var filtrex = (function (exports) { if (arguments.length > 2) throw new TypeError('Too many arguments.') options = typeof options === "object" ? options : {}; - let {extraFunctions, customProp, operators} = options; + + const knownOptions = ['extraFunctions', 'constants', 'customProp', 'operators']; + let {extraFunctions, constants, customProp, operators} = options; + for (const key of Object.keys(options)) - { - if (!(["extraFunctions", "customProp", "operators"].includes(key))) - throw new TypeError(`Unknown option: ${key}`) - } + if (!knownOptions.includes(key)) throw new UnknownOptionError(key) @@ -1945,9 +2091,10 @@ var filtrex = (function (exports) { ceil: Math.ceil, floor: Math.floor, log: Math.log, + log2: Math.log2, + log10: Math.log10, max: Math.max, min: Math.min, - random: Math.random, round: Math.round, sqrt: Math.sqrt, exists: (v) => v !== undefined && v !== null, @@ -1988,6 +2135,8 @@ var filtrex = (function (exports) { operators = defaultOperators; + constants = constants ?? {}; + // Compile the expression @@ -1999,35 +2148,42 @@ var filtrex = (function (exports) { // Metaprogramming functions - function prop(name, obj) { - if (hasOwnProperty(obj||{}, name)) + function nakedProp(name, obj) { + if (hasOwnProperty(obj ?? {}, name)) return obj[name] - throw new ReferenceError(`Property “${name}” does not exist.`) + throw new UnknownPropertyError(name) } function safeGetter(obj) { return function get(name) { - if (hasOwnProperty(obj||{}, name)) + if (hasOwnProperty(obj ?? {}, name)) return obj[name] - throw new ReferenceError(`Property “${name}” does not exist.`) + throw new UnknownPropertyError(name) } } if (typeof customProp === 'function') { - prop = (name, obj) => customProp(name, safeGetter(obj), obj); + nakedProp = (name, obj) => customProp(name, safeGetter(obj), obj); } function createCall(fns) { - return function call(name, ...args) { + return function call({ name }, ...args) { if (hasOwnProperty(fns, name) && typeof fns[name] === "function") return fns[name](...args) - throw new ReferenceError(`Unknown function: ${name}()`) + throw new UnknownFunctionError(name) } } + function prop({ name, type }, obj) { + if (type === 'unescaped' && hasOwnProperty(constants, name)) + return constants[name] + + return nakedProp(name, obj) + } + // Patch together and return diff --git a/dist/browser/filtrex.min.js b/dist/browser/filtrex.min.js index 3939e7e..78dd7c7 100644 --- a/dist/browser/filtrex.min.js +++ b/dist/browser/filtrex.min.js @@ -1 +1 @@ -var filtrex=function(t){"use strict";var e=function(){var t={trace:function(){},yy:{},symbols_:{error:2,expressions:3,e:4,EOF:5,"-":6,"+":7,"*":8,"/":9,"%":10,"^":11,and:12,or:13,not:14,if:15,then:16,else:17,in:18,notIn:19,"(":20,")":21,Arguments:22,",":23,Number:24,Symbol:25,String:26,of:27,Relation:28,RelationalOperator:29,"==":30,"!=":31,"~=":32,"<":33,"<=":34,">=":35,">":36,$accept:0,$end:1},terminals_:{2:"error",5:"EOF",6:"-",7:"+",8:"*",9:"/",10:"%",11:"^",12:"and",13:"or",14:"not",15:"if",16:"then",17:"else",18:"in",19:"notIn",20:"(",21:")",23:",",24:"Number",25:"Symbol",26:"String",27:"of",30:"==",31:"!=",32:"~=",33:"<",34:"<=",35:">=",36:">"},productions_:[0,[3,2],[4,2],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,2],[4,6],[4,3],[4,3],[4,3],[4,5],[4,1],[4,1],[4,1],[4,3],[4,3],[4,4],[4,1],[29,1],[29,1],[29,1],[29,1],[29,1],[29,1],[29,1],[28,3],[28,3],[22,1],[22,3]],performAction:function(t,e,n,r,i,s,o){var h=s.length-1;switch(i){case 1:return s[h-1];case 2:this.$=["(","ops['-'](",s[h],")",")"];break;case 3:case 4:case 5:case 6:case 7:case 8:this.$=["(","ops['",s[h-1],"'](",s[h-2],", ",s[h],")",")"];break;case 9:this.$=["(","","std.coerceBoolean","(",s[h-2],") && ","std.coerceBoolean","(",s[h],")",")"];break;case 10:this.$=["(","","std.coerceBoolean","(",s[h-2],") || ","std.coerceBoolean","(",s[h],")",")"];break;case 11:this.$=["(","! ","std.coerceBoolean","(",s[h],")",")"];break;case 12:this.$=["(","","std.coerceBoolean","(",s[h-4],") ? ",s[h-2]," : ",s[h],"",")"];break;case 13:this.$=["(","std.isSubset(",s[h-2],", ",s[h],")",")"];break;case 14:this.$=["(","!std.isSubset(",s[h-2],", ",s[h],")",")"];break;case 15:this.$=["(","",s[h-1],"",")"];break;case 16:this.$=["(","[ ",s[h-3],", ",s[h-1]," ]",")"];break;case 17:this.$=["",s[h],""];break;case 18:this.$=["prop(",s[h],", data)"];break;case 19:this.$=["",s[h],""];break;case 20:this.$=["prop(",s[h-2],", ",s[h],")"];break;case 21:this.$=["call(",s[h-2],")"];break;case 22:this.$=["call(",s[h-3],", ",s[h-1],")"];break;case 23:this.$=r.reduceRelation(s[h]);break;case 24:this.$=["=="];break;case 25:this.$=["!="];break;case 26:this.$=["~="];break;case 27:this.$=["<"];break;case 28:this.$=["<="];break;case 29:this.$=[">="];break;case 30:this.$=[">"];break;case 31:this.$=[s[h-2],s[h-1],...s[h]];break;case 32:this.$=[s[h-2],s[h-1],s[h]];break;case 33:this.$=["",s[h],""];break;case 34:this.$=["",s[h-2],", ",s[h],""]}},table:[{3:1,4:2,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{1:[3]},{5:[1,11],6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[1,19],18:[1,20],19:[1,21],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{4:30,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:31,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:32,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:33,6:[1,3],14:[1,4],15:[1,5],20:[1,6],22:34,24:[1,7],25:[1,8],26:[1,9],28:10},{5:[2,17],6:[2,17],7:[2,17],8:[2,17],9:[2,17],10:[2,17],11:[2,17],12:[2,17],13:[2,17],16:[2,17],17:[2,17],18:[2,17],19:[2,17],21:[2,17],23:[2,17],30:[2,17],31:[2,17],32:[2,17],33:[2,17],34:[2,17],35:[2,17],36:[2,17]},{5:[2,18],6:[2,18],7:[2,18],8:[2,18],9:[2,18],10:[2,18],11:[2,18],12:[2,18],13:[2,18],16:[2,18],17:[2,18],18:[2,18],19:[2,18],20:[1,36],21:[2,18],23:[2,18],27:[1,35],30:[2,18],31:[2,18],32:[2,18],33:[2,18],34:[2,18],35:[2,18],36:[2,18]},{5:[2,19],6:[2,19],7:[2,19],8:[2,19],9:[2,19],10:[2,19],11:[2,19],12:[2,19],13:[2,19],16:[2,19],17:[2,19],18:[2,19],19:[2,19],21:[2,19],23:[2,19],30:[2,19],31:[2,19],32:[2,19],33:[2,19],34:[2,19],35:[2,19],36:[2,19]},{5:[2,23],6:[2,23],7:[2,23],8:[2,23],9:[2,23],10:[2,23],11:[2,23],12:[2,23],13:[2,23],16:[2,23],17:[2,23],18:[2,23],19:[2,23],21:[2,23],23:[2,23],30:[2,23],31:[2,23],32:[2,23],33:[2,23],34:[2,23],35:[2,23],36:[2,23]},{1:[2,1]},{4:37,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:38,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:39,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:40,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:41,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:42,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:43,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:44,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:45,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:46,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:48,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:47},{6:[2,24],14:[2,24],15:[2,24],20:[2,24],24:[2,24],25:[2,24],26:[2,24]},{6:[2,25],14:[2,25],15:[2,25],20:[2,25],24:[2,25],25:[2,25],26:[2,25]},{6:[2,26],14:[2,26],15:[2,26],20:[2,26],24:[2,26],25:[2,26],26:[2,26]},{6:[2,27],14:[2,27],15:[2,27],20:[2,27],24:[2,27],25:[2,27],26:[2,27]},{6:[2,28],14:[2,28],15:[2,28],20:[2,28],24:[2,28],25:[2,28],26:[2,28]},{6:[2,29],14:[2,29],15:[2,29],20:[2,29],24:[2,29],25:[2,29],26:[2,29]},{6:[2,30],14:[2,30],15:[2,30],20:[2,30],24:[2,30],25:[2,30],26:[2,30]},{5:[2,2],6:[2,2],7:[2,2],8:[2,2],9:[2,2],10:[2,2],11:[1,17],12:[2,2],13:[2,2],16:[2,2],17:[2,2],18:[2,2],19:[2,2],21:[2,2],23:[2,2],29:22,30:[2,2],31:[2,2],32:[2,2],33:[2,2],34:[2,2],35:[2,2],36:[2,2]},{5:[2,11],6:[2,11],7:[2,11],8:[2,11],9:[2,11],10:[2,11],11:[1,17],12:[2,11],13:[2,11],16:[2,11],17:[2,11],18:[2,11],19:[2,11],21:[2,11],23:[2,11],29:22,30:[2,11],31:[2,11],32:[2,11],33:[2,11],34:[2,11],35:[2,11],36:[2,11]},{6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[1,19],16:[1,49],18:[1,20],19:[1,21],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[1,19],18:[1,20],19:[1,21],21:[1,50],23:[2,33],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{23:[1,51]},{4:52,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:55,6:[1,3],14:[1,4],15:[1,5],20:[1,6],21:[1,53],22:54,24:[1,7],25:[1,8],26:[1,9],28:10},{5:[2,3],6:[2,3],7:[2,3],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[2,3],13:[2,3],16:[2,3],17:[2,3],18:[2,3],19:[2,3],21:[2,3],23:[2,3],29:22,30:[2,3],31:[2,3],32:[2,3],33:[2,3],34:[2,3],35:[2,3],36:[2,3]},{5:[2,4],6:[2,4],7:[2,4],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[2,4],13:[2,4],16:[2,4],17:[2,4],18:[2,4],19:[2,4],21:[2,4],23:[2,4],29:22,30:[2,4],31:[2,4],32:[2,4],33:[2,4],34:[2,4],35:[2,4],36:[2,4]},{5:[2,5],6:[2,5],7:[2,5],8:[2,5],9:[2,5],10:[2,5],11:[1,17],12:[2,5],13:[2,5],16:[2,5],17:[2,5],18:[2,5],19:[2,5],21:[2,5],23:[2,5],29:22,30:[2,5],31:[2,5],32:[2,5],33:[2,5],34:[2,5],35:[2,5],36:[2,5]},{5:[2,6],6:[2,6],7:[2,6],8:[2,6],9:[2,6],10:[2,6],11:[1,17],12:[2,6],13:[2,6],16:[2,6],17:[2,6],18:[2,6],19:[2,6],21:[2,6],23:[2,6],29:22,30:[2,6],31:[2,6],32:[2,6],33:[2,6],34:[2,6],35:[2,6],36:[2,6]},{5:[2,7],6:[2,7],7:[2,7],8:[2,7],9:[2,7],10:[2,7],11:[1,17],12:[2,7],13:[2,7],16:[2,7],17:[2,7],18:[2,7],19:[2,7],21:[2,7],23:[2,7],29:22,30:[2,7],31:[2,7],32:[2,7],33:[2,7],34:[2,7],35:[2,7],36:[2,7]},{5:[2,8],6:[2,8],7:[2,8],8:[2,8],9:[2,8],10:[2,8],11:[1,17],12:[2,8],13:[2,8],16:[2,8],17:[2,8],18:[2,8],19:[2,8],21:[2,8],23:[2,8],29:22,30:[2,8],31:[2,8],32:[2,8],33:[2,8],34:[2,8],35:[2,8],36:[2,8]},{5:[2,9],6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[2,9],13:[2,9],16:[2,9],17:[2,9],18:[1,20],19:[1,21],21:[2,9],23:[2,9],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{5:[2,10],6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[2,10],16:[2,10],17:[2,10],18:[1,20],19:[1,21],21:[2,10],23:[2,10],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{5:[2,13],6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[2,13],13:[2,13],16:[2,13],17:[2,13],18:[2,13],19:[2,13],21:[2,13],23:[2,13],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{5:[2,14],6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[2,14],13:[2,14],16:[2,14],17:[2,14],18:[2,14],19:[2,14],21:[2,14],23:[2,14],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{5:[2,31],6:[2,31],7:[2,31],8:[2,31],9:[2,31],10:[2,31],11:[2,31],12:[2,31],13:[2,31],16:[2,31],17:[2,31],18:[2,31],19:[2,31],21:[2,31],23:[2,31],30:[2,31],31:[2,31],32:[2,31],33:[2,31],34:[2,31],35:[2,31],36:[2,31]},{5:[2,32],6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[2,32],13:[2,32],16:[2,32],17:[2,32],18:[2,32],19:[2,32],21:[2,32],23:[2,32],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{4:56,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{5:[2,15],6:[2,15],7:[2,15],8:[2,15],9:[2,15],10:[2,15],11:[2,15],12:[2,15],13:[2,15],16:[2,15],17:[2,15],18:[2,15],19:[2,15],21:[2,15],23:[2,15],30:[2,15],31:[2,15],32:[2,15],33:[2,15],34:[2,15],35:[2,15],36:[2,15]},{4:57,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{5:[2,20],6:[2,20],7:[2,20],8:[2,20],9:[2,20],10:[2,20],11:[2,20],12:[2,20],13:[2,20],16:[2,20],17:[2,20],18:[2,20],19:[2,20],21:[2,20],23:[2,20],29:22,30:[2,20],31:[2,20],32:[2,20],33:[2,20],34:[2,20],35:[2,20],36:[2,20]},{5:[2,21],6:[2,21],7:[2,21],8:[2,21],9:[2,21],10:[2,21],11:[2,21],12:[2,21],13:[2,21],16:[2,21],17:[2,21],18:[2,21],19:[2,21],21:[2,21],23:[2,21],30:[2,21],31:[2,21],32:[2,21],33:[2,21],34:[2,21],35:[2,21],36:[2,21]},{21:[1,58],23:[1,59]},{6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[1,19],18:[1,20],19:[1,21],21:[2,33],23:[2,33],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[1,19],17:[1,60],18:[1,20],19:[1,21],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[1,19],18:[1,20],19:[1,21],21:[1,61],23:[2,34],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{5:[2,22],6:[2,22],7:[2,22],8:[2,22],9:[2,22],10:[2,22],11:[2,22],12:[2,22],13:[2,22],16:[2,22],17:[2,22],18:[2,22],19:[2,22],21:[2,22],23:[2,22],30:[2,22],31:[2,22],32:[2,22],33:[2,22],34:[2,22],35:[2,22],36:[2,22]},{4:62,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:63,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{5:[2,16],6:[2,16],7:[2,16],8:[2,16],9:[2,16],10:[2,16],11:[2,16],12:[2,16],13:[2,16],16:[2,16],17:[2,16],18:[2,16],19:[2,16],21:[2,16],23:[2,16],30:[2,16],31:[2,16],32:[2,16],33:[2,16],34:[2,16],35:[2,16],36:[2,16]},{6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[1,19],18:[1,20],19:[1,21],21:[2,34],23:[2,34],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{5:[2,12],6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[1,19],16:[2,12],17:[2,12],18:[1,20],19:[1,21],21:[2,12],23:[2,12],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]}],defaultActions:{11:[2,1]},parseError:function(t,e){throw new Error(t)},parse:function(t){var e=this,n=[0],r=[null],i=[],s=this.table,o="",h=0,l=0,a=0;this.lexer.setInput(t),this.lexer.yy=this.yy,this.yy.lexer=this.lexer,this.yy.parser=this,void 0===this.lexer.yylloc&&(this.lexer.yylloc={});var c=this.lexer.yylloc;i.push(c);var u=this.lexer.options&&this.lexer.options.ranges;function y(){var t;return"number"!=typeof(t=e.lexer.lex()||1)&&(t=e.symbols_[t]||t),t}"function"==typeof this.yy.parseError&&(this.parseError=this.yy.parseError);for(var f,p,g,d,m,b,x,w,_,k,$={};;){if(g=n[n.length-1],this.defaultActions[g]?d=this.defaultActions[g]:(null==f&&(f=y()),d=s[g]&&s[g][f]),void 0===d||!d.length||!d[0]){var E="";if(!a){for(b in _=[],s[g])this.terminals_[b]&&b>2&&_.push("'"+this.terminals_[b]+"'");E=this.lexer.showPosition?"Parse error on line "+(h+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+_.join(", ")+", got '"+(this.terminals_[f]||f)+"'":"Parse error on line "+(h+1)+": Unexpected "+(1==f?"end of input":"'"+(this.terminals_[f]||f)+"'"),this.parseError(E,{text:this.lexer.match,token:this.terminals_[f]||f,line:this.lexer.yylineno,loc:c,expected:_})}if(3==a){if(1==f)throw new Error(E||"Parsing halted.");l=this.lexer.yyleng,o=this.lexer.yytext,h=this.lexer.yylineno,c=this.lexer.yylloc,f=y()}for(;!(2..toString()in s[g]);){if(0===g)throw new Error(E||"Parsing halted.");k=1,n.length=n.length-2*k,r.length=r.length-k,i.length=i.length-k,g=n[n.length-1]}p=2==f?null:f,f=2,d=s[g=n[n.length-1]]&&s[g][2],a=3}if(d[0]instanceof Array&&d.length>1)throw new Error("Parse Error: multiple actions possible at state: "+g+", token: "+f);switch(d[0]){case 1:n.push(f),r.push(this.lexer.yytext),i.push(this.lexer.yylloc),n.push(d[1]),f=null,p?(f=p,p=null):(l=this.lexer.yyleng,o=this.lexer.yytext,h=this.lexer.yylineno,c=this.lexer.yylloc,a>0&&a--);break;case 2:if(x=this.productions_[d[1]][1],$.$=r[r.length-x],$._$={first_line:i[i.length-(x||1)].first_line,last_line:i[i.length-1].last_line,first_column:i[i.length-(x||1)].first_column,last_column:i[i.length-1].last_column},u&&($._$.range=[i[i.length-(x||1)].range[0],i[i.length-1].range[1]]),void 0!==(m=this.performAction.call($,o,l,h,this.yy,d[1],r,i)))return m;x&&(n=n.slice(0,-1*x*2),r=r.slice(0,-1*x),i=i.slice(0,-1*x)),n.push(this.productions_[d[1]][0]),r.push($.$),i.push($._$),w=s[n[n.length-2]][n[n.length-1]],n.push(w);break;case 3:return!0}}return!0}},e=function(){var t={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t){return this._input=t,this._more=this._less=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e-1),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this},more:function(){return this._more=!0,this},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},next:function(){if(this.done)return this.EOF;var t,e,n,r,i;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var s=this._currentRules(),o=0;oe[0].length)||(e=n,r=o,this.options.flex));o++);return e?((i=e[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=i.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:i?i[i.length-1].length-i[i.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+e[0].length},this.yytext+=e[0],this.match+=e[0],this.matches=e,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._input=this._input.slice(e[0].length),this.matched+=e[0],t=this.performAction.call(this,this.yy,this,s[r],this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),t||void 0):""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return void 0!==t?t:this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.pop()},_currentRules:function(){return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules},topState:function(){return this.conditionStack[this.conditionStack.length-2]},pushState:function(t){this.begin(t)},options:{},performAction:function(t,e,n,r){switch(n){case 0:return"*";case 1:return"/";case 2:return"-";case 3:return"+";case 4:return"^";case 5:return"%";case 6:return"(";case 7:return")";case 8:return",";case 9:return"==";case 10:return"!=";case 11:return"~=";case 12:return">=";case 13:return"<=";case 14:return"<";case 15:return">";case 16:return"notIn";case 17:return"and";case 18:return"or";case 19:return"not";case 20:return"in";case 21:return"of";case 22:return"if";case 23:return"then";case 24:return"else";case 25:break;case 26:return"Number";case 27:return e.yytext=JSON.stringify(e.yytext),"Symbol";case 28:return e.yytext=t.buildString("'",e.yytext),"Symbol";case 29:return e.yytext=t.buildString('"',e.yytext),"String";case 30:return"EOF"}},rules:[/^(?:\*)/,/^(?:\/)/,/^(?:-)/,/^(?:\+)/,/^(?:\^)/,/^(?:\%)/,/^(?:\()/,/^(?:\))/,/^(?:\,)/,/^(?:==)/,/^(?:\!=)/,/^(?:\~=)/,/^(?:>=)/,/^(?:<=)/,/^(?:<)/,/^(?:>)/,/^(?:not\s+in[^\w])/,/^(?:and[^\w])/,/^(?:or[^\w])/,/^(?:not[^\w])/,/^(?:in[^\w])/,/^(?:of[^\w])/,/^(?:if[^\w])/,/^(?:then[^\w])/,/^(?:else[^\w])/,/^(?:\s+)/,/^(?:[0-9]+(?:\.[0-9]+)?(?![0-9\.]))/,/^(?:[a-zA-Z$_][\.a-zA-Z0-9$_]*)/,/^(?:'(?:\\'|\\\\|[^'\\])*')/,/^(?:"(?:\\"|\\\\|[^"\\])*")/,/^(?:$)/],conditions:{INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30],inclusive:!0}}};return t}();function n(){this.yy={}}return t.lexer=e,n.prototype=t,t.Parser=n,new n}();const n=e;function r(t,e){return("object"==typeof t||"function"==typeof t)&&Object.prototype.hasOwnProperty.call(t,e)}function i(t,e){return(t%e+e)%e}function s(t){return Array.isArray(t)&&1===t.length&&(t=t[0]),function(t){return"object"!=typeof t?t:t instanceof Number||t instanceof String||t instanceof Boolean?t.valueOf():void 0}(t)}function o(t){return void 0===(t=s(t))?"undefined":null===t?"null":!0===t?"true":!1===t?"false":"number"==typeof t?"number":"string"==typeof t?"text":"object"!=typeof t&&"function"!=typeof t?"unknown type":Array.isArray(t)?"list":"object"}function h(t){if("number"==typeof(t=s(t)))return t;throw new TypeError(`Expected a number, but got a ${o(t)} instead.`)}function l(t){if("string"==typeof(t=s(t)))return t;throw new TypeError(`Expected a text, but got a ${o(t)} instead.`)}function a(t){if("string"==typeof(t=s(t))||"number"==typeof t)return t;throw new TypeError(`Expected a text or a number, but got a ${o(t)} instead.`)}function c(t){if(null==t)throw new TypeError(`Expected a list, but got ${t} instead.`);return Array.isArray(t)?t:[t]}function u(t){const e=[...t],n=[];for(;e.length;){const t=e.pop();Array.isArray(t)?e.push(...t):n.push(t)}return n.reverse()}e.Parser;const y={isfn:(t,e)=>r(t,e)&&"function"==typeof t[e],unknown(t){throw new ReferenceError("Unknown function: "+t+"()")},coerceArray:c,coerceNumber:h,coerceNumberOrString:a,coerceBoolean:function(t){if("boolean"==typeof(t=s(t)))return t;throw new TypeError(`Expected a logical value (“true” or “false”), but got a ${o(t)} instead.`)},isSubset(t,e){const n=c(t),r=c(e);return n.every(t=>r.includes(t))},buildString(t,e){t=String(t)[0];let n="";if((e=String(e))[0]!==t||e[e.length-1]!==t)throw new Error("Unexpected internal error: String literal doesn't begin/end with the right quotation mark.");for(let r=1;r=e.length-1)throw new Error("Unexpected internal error: Unescaped backslash at the end of string literal.");if("\\"===e[r])n+="\\";else{if(e[r]!==t)throw new Error("Unexpected internal error: Invalid escaped character in string literal: "+e[r]);n+=t}}else{if(e[r]===t)throw new Error("Unexpected internal error: String literal contains unescaped quotation mark.");n+=e[r]}return JSON.stringify(n)},reduceRelation(t){const e=[],n=[];let r=u([t[0]]).join(""),i=0;for(let s=1;s2)throw new TypeError("Too many arguments.");e="object"==typeof e?e:{};let{extraFunctions:s,customProp:o,operators:c}=e;for(const t of Object.keys(e))if(!["extraFunctions","customProp","operators"].includes(t))throw new TypeError("Unknown option: "+t);let f={abs:Math.abs,ceil:Math.ceil,floor:Math.floor,log:Math.log,max:Math.max,min:Math.min,random:Math.random,round:Math.round,sqrt:Math.sqrt,exists:t=>null!=t,empty:t=>null==t||""===t||Array.isArray(t)&&0===t.length};if(s)for(const t of Object.keys(s))f[t]=s[t];let p={"+":(t,e)=>a(t)+a(e),"-":(t,e)=>void 0===e?-h(t):h(t)-h(e),"*":(t,e)=>h(t)*h(e),"/":(t,e)=>h(t)/h(e),"%":(t,e)=>i(h(t),h(e)),"^":(t,e)=>Math.pow(h(t),h(e)),"==":(t,e)=>t===e,"!=":(t,e)=>t!==e,"<":(t,e)=>h(t)h(t)<=h(e),">=":(t,e)=>h(t)>=h(e),">":(t,e)=>h(t)>h(e),"~=":(t,e)=>RegExp(l(e)).test(l(t))};if(c)for(const t of Object.keys(c))p[t]=c[t];c=p;let g=u(n.parse(t));function d(t,e){if(r(e||{},t))return e[t];throw new ReferenceError(`Property “${t}” does not exist.`)}function m(t){return function(e){if(r(t||{},e))return t[e];throw new ReferenceError(`Property “${e}” does not exist.`)}}function b(t){return function(e,...n){if(r(t,e)&&"function"==typeof t[e])return t[e](...n);throw new ReferenceError(`Unknown function: ${e}()`)}}g.unshift("return "),g.push(";"),"function"==typeof o&&(d=(t,e)=>o(t,m(e),e));let x=new Function("call","ops","std","prop","data",g.join(""));return function(t){try{return x(b(f),c,y,d,t)}catch(t){return t}}},Object.defineProperty(t,"__esModule",{value:!0}),t}({}); \ No newline at end of file +var filtrex=function(t){"use strict";var e=function(){var t={trace:function(){},yy:{},symbols_:{error:2,expressions:3,e:4,EOF:5,"-":6,"+":7,"*":8,"/":9,"%":10,"^":11,and:12,or:13,not:14,if:15,then:16,else:17,in:18,notIn:19,"(":20,")":21,Arguments:22,",":23,Number:24,Symbol:25,String:26,of:27,Relation:28,RelationalOperator:29,"==":30,"!=":31,"~=":32,"<":33,"<=":34,">=":35,">":36,$accept:0,$end:1},terminals_:{2:"error",5:"EOF",6:"-",7:"+",8:"*",9:"/",10:"%",11:"^",12:"and",13:"or",14:"not",15:"if",16:"then",17:"else",18:"in",19:"notIn",20:"(",21:")",23:",",24:"Number",25:"Symbol",26:"String",27:"of",30:"==",31:"!=",32:"~=",33:"<",34:"<=",35:">=",36:">"},productions_:[0,[3,2],[4,2],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,2],[4,6],[4,3],[4,3],[4,3],[4,5],[4,1],[4,1],[4,1],[4,3],[4,3],[4,4],[4,1],[29,1],[29,1],[29,1],[29,1],[29,1],[29,1],[29,1],[28,3],[28,3],[22,1],[22,3]],performAction:function(t,e,n,r,s,i,o){var l=i.length-1;switch(s){case 1:return i[l-1];case 2:this.$=["(","ops['-'](",i[l],")",")"];break;case 3:case 4:case 5:case 6:case 7:case 8:this.$=["(","ops['",i[l-1],"'](",i[l-2],", ",i[l],")",")"];break;case 9:this.$=["(","","std.coerceBoolean","(",i[l-2],") && ","std.coerceBoolean","(",i[l],")",")"];break;case 10:this.$=["(","","std.coerceBoolean","(",i[l-2],") || ","std.coerceBoolean","(",i[l],")",")"];break;case 11:this.$=["(","! ","std.coerceBoolean","(",i[l],")",")"];break;case 12:this.$=["(","","std.coerceBoolean","(",i[l-4],") ? ",i[l-2]," : ",i[l],"",")"];break;case 13:this.$=["(","std.isSubset(",i[l-2],", ",i[l],")",")"];break;case 14:this.$=["(","!std.isSubset(",i[l-2],", ",i[l],")",")"];break;case 15:this.$=["(","",i[l-1],"",")"];break;case 16:this.$=["(","[ ",i[l-3],", ",i[l-1]," ]",")"];break;case 17:this.$=["",i[l],""];break;case 18:this.$=["prop(",i[l],", data)"];break;case 19:this.$=["",i[l],""];break;case 20:this.$=["prop(",i[l-2],", ",i[l],")"];break;case 21:this.$=["call(",i[l-2],")"];break;case 22:this.$=["call(",i[l-3],", ",i[l-1],")"];break;case 23:this.$=r.reduceRelation(i[l]);break;case 24:this.$=["=="];break;case 25:this.$=["!="];break;case 26:this.$=["~="];break;case 27:this.$=["<"];break;case 28:this.$=["<="];break;case 29:this.$=[">="];break;case 30:this.$=[">"];break;case 31:this.$=[i[l-2],i[l-1],...i[l]];break;case 32:this.$=[i[l-2],i[l-1],i[l]];break;case 33:this.$=["",i[l],""];break;case 34:this.$=["",i[l-2],", ",i[l],""]}},table:[{3:1,4:2,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{1:[3]},{5:[1,11],6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[1,19],18:[1,20],19:[1,21],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{4:30,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:31,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:32,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:33,6:[1,3],14:[1,4],15:[1,5],20:[1,6],22:34,24:[1,7],25:[1,8],26:[1,9],28:10},{5:[2,17],6:[2,17],7:[2,17],8:[2,17],9:[2,17],10:[2,17],11:[2,17],12:[2,17],13:[2,17],16:[2,17],17:[2,17],18:[2,17],19:[2,17],21:[2,17],23:[2,17],30:[2,17],31:[2,17],32:[2,17],33:[2,17],34:[2,17],35:[2,17],36:[2,17]},{5:[2,18],6:[2,18],7:[2,18],8:[2,18],9:[2,18],10:[2,18],11:[2,18],12:[2,18],13:[2,18],16:[2,18],17:[2,18],18:[2,18],19:[2,18],20:[1,36],21:[2,18],23:[2,18],27:[1,35],30:[2,18],31:[2,18],32:[2,18],33:[2,18],34:[2,18],35:[2,18],36:[2,18]},{5:[2,19],6:[2,19],7:[2,19],8:[2,19],9:[2,19],10:[2,19],11:[2,19],12:[2,19],13:[2,19],16:[2,19],17:[2,19],18:[2,19],19:[2,19],21:[2,19],23:[2,19],30:[2,19],31:[2,19],32:[2,19],33:[2,19],34:[2,19],35:[2,19],36:[2,19]},{5:[2,23],6:[2,23],7:[2,23],8:[2,23],9:[2,23],10:[2,23],11:[2,23],12:[2,23],13:[2,23],16:[2,23],17:[2,23],18:[2,23],19:[2,23],21:[2,23],23:[2,23],30:[2,23],31:[2,23],32:[2,23],33:[2,23],34:[2,23],35:[2,23],36:[2,23]},{1:[2,1]},{4:37,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:38,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:39,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:40,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:41,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:42,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:43,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:44,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:45,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:46,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:48,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:47},{6:[2,24],14:[2,24],15:[2,24],20:[2,24],24:[2,24],25:[2,24],26:[2,24]},{6:[2,25],14:[2,25],15:[2,25],20:[2,25],24:[2,25],25:[2,25],26:[2,25]},{6:[2,26],14:[2,26],15:[2,26],20:[2,26],24:[2,26],25:[2,26],26:[2,26]},{6:[2,27],14:[2,27],15:[2,27],20:[2,27],24:[2,27],25:[2,27],26:[2,27]},{6:[2,28],14:[2,28],15:[2,28],20:[2,28],24:[2,28],25:[2,28],26:[2,28]},{6:[2,29],14:[2,29],15:[2,29],20:[2,29],24:[2,29],25:[2,29],26:[2,29]},{6:[2,30],14:[2,30],15:[2,30],20:[2,30],24:[2,30],25:[2,30],26:[2,30]},{5:[2,2],6:[2,2],7:[2,2],8:[2,2],9:[2,2],10:[2,2],11:[1,17],12:[2,2],13:[2,2],16:[2,2],17:[2,2],18:[2,2],19:[2,2],21:[2,2],23:[2,2],29:22,30:[2,2],31:[2,2],32:[2,2],33:[2,2],34:[2,2],35:[2,2],36:[2,2]},{5:[2,11],6:[2,11],7:[2,11],8:[2,11],9:[2,11],10:[2,11],11:[1,17],12:[2,11],13:[2,11],16:[2,11],17:[2,11],18:[2,11],19:[2,11],21:[2,11],23:[2,11],29:22,30:[2,11],31:[2,11],32:[2,11],33:[2,11],34:[2,11],35:[2,11],36:[2,11]},{6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[1,19],16:[1,49],18:[1,20],19:[1,21],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[1,19],18:[1,20],19:[1,21],21:[1,50],23:[2,33],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{23:[1,51]},{4:52,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:55,6:[1,3],14:[1,4],15:[1,5],20:[1,6],21:[1,53],22:54,24:[1,7],25:[1,8],26:[1,9],28:10},{5:[2,3],6:[2,3],7:[2,3],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[2,3],13:[2,3],16:[2,3],17:[2,3],18:[2,3],19:[2,3],21:[2,3],23:[2,3],29:22,30:[2,3],31:[2,3],32:[2,3],33:[2,3],34:[2,3],35:[2,3],36:[2,3]},{5:[2,4],6:[2,4],7:[2,4],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[2,4],13:[2,4],16:[2,4],17:[2,4],18:[2,4],19:[2,4],21:[2,4],23:[2,4],29:22,30:[2,4],31:[2,4],32:[2,4],33:[2,4],34:[2,4],35:[2,4],36:[2,4]},{5:[2,5],6:[2,5],7:[2,5],8:[2,5],9:[2,5],10:[2,5],11:[1,17],12:[2,5],13:[2,5],16:[2,5],17:[2,5],18:[2,5],19:[2,5],21:[2,5],23:[2,5],29:22,30:[2,5],31:[2,5],32:[2,5],33:[2,5],34:[2,5],35:[2,5],36:[2,5]},{5:[2,6],6:[2,6],7:[2,6],8:[2,6],9:[2,6],10:[2,6],11:[1,17],12:[2,6],13:[2,6],16:[2,6],17:[2,6],18:[2,6],19:[2,6],21:[2,6],23:[2,6],29:22,30:[2,6],31:[2,6],32:[2,6],33:[2,6],34:[2,6],35:[2,6],36:[2,6]},{5:[2,7],6:[2,7],7:[2,7],8:[2,7],9:[2,7],10:[2,7],11:[1,17],12:[2,7],13:[2,7],16:[2,7],17:[2,7],18:[2,7],19:[2,7],21:[2,7],23:[2,7],29:22,30:[2,7],31:[2,7],32:[2,7],33:[2,7],34:[2,7],35:[2,7],36:[2,7]},{5:[2,8],6:[2,8],7:[2,8],8:[2,8],9:[2,8],10:[2,8],11:[1,17],12:[2,8],13:[2,8],16:[2,8],17:[2,8],18:[2,8],19:[2,8],21:[2,8],23:[2,8],29:22,30:[2,8],31:[2,8],32:[2,8],33:[2,8],34:[2,8],35:[2,8],36:[2,8]},{5:[2,9],6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[2,9],13:[2,9],16:[2,9],17:[2,9],18:[1,20],19:[1,21],21:[2,9],23:[2,9],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{5:[2,10],6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[2,10],16:[2,10],17:[2,10],18:[1,20],19:[1,21],21:[2,10],23:[2,10],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{5:[2,13],6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[2,13],13:[2,13],16:[2,13],17:[2,13],18:[2,13],19:[2,13],21:[2,13],23:[2,13],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{5:[2,14],6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[2,14],13:[2,14],16:[2,14],17:[2,14],18:[2,14],19:[2,14],21:[2,14],23:[2,14],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{5:[2,31],6:[2,31],7:[2,31],8:[2,31],9:[2,31],10:[2,31],11:[2,31],12:[2,31],13:[2,31],16:[2,31],17:[2,31],18:[2,31],19:[2,31],21:[2,31],23:[2,31],30:[2,31],31:[2,31],32:[2,31],33:[2,31],34:[2,31],35:[2,31],36:[2,31]},{5:[2,32],6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[2,32],13:[2,32],16:[2,32],17:[2,32],18:[2,32],19:[2,32],21:[2,32],23:[2,32],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{4:56,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{5:[2,15],6:[2,15],7:[2,15],8:[2,15],9:[2,15],10:[2,15],11:[2,15],12:[2,15],13:[2,15],16:[2,15],17:[2,15],18:[2,15],19:[2,15],21:[2,15],23:[2,15],30:[2,15],31:[2,15],32:[2,15],33:[2,15],34:[2,15],35:[2,15],36:[2,15]},{4:57,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{5:[2,20],6:[2,20],7:[2,20],8:[2,20],9:[2,20],10:[2,20],11:[2,20],12:[2,20],13:[2,20],16:[2,20],17:[2,20],18:[2,20],19:[2,20],21:[2,20],23:[2,20],29:22,30:[2,20],31:[2,20],32:[2,20],33:[2,20],34:[2,20],35:[2,20],36:[2,20]},{5:[2,21],6:[2,21],7:[2,21],8:[2,21],9:[2,21],10:[2,21],11:[2,21],12:[2,21],13:[2,21],16:[2,21],17:[2,21],18:[2,21],19:[2,21],21:[2,21],23:[2,21],30:[2,21],31:[2,21],32:[2,21],33:[2,21],34:[2,21],35:[2,21],36:[2,21]},{21:[1,58],23:[1,59]},{6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[1,19],18:[1,20],19:[1,21],21:[2,33],23:[2,33],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[1,19],17:[1,60],18:[1,20],19:[1,21],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[1,19],18:[1,20],19:[1,21],21:[1,61],23:[2,34],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{5:[2,22],6:[2,22],7:[2,22],8:[2,22],9:[2,22],10:[2,22],11:[2,22],12:[2,22],13:[2,22],16:[2,22],17:[2,22],18:[2,22],19:[2,22],21:[2,22],23:[2,22],30:[2,22],31:[2,22],32:[2,22],33:[2,22],34:[2,22],35:[2,22],36:[2,22]},{4:62,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{4:63,6:[1,3],14:[1,4],15:[1,5],20:[1,6],24:[1,7],25:[1,8],26:[1,9],28:10},{5:[2,16],6:[2,16],7:[2,16],8:[2,16],9:[2,16],10:[2,16],11:[2,16],12:[2,16],13:[2,16],16:[2,16],17:[2,16],18:[2,16],19:[2,16],21:[2,16],23:[2,16],30:[2,16],31:[2,16],32:[2,16],33:[2,16],34:[2,16],35:[2,16],36:[2,16]},{6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[1,19],18:[1,20],19:[1,21],21:[2,34],23:[2,34],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]},{5:[2,12],6:[1,13],7:[1,12],8:[1,14],9:[1,15],10:[1,16],11:[1,17],12:[1,18],13:[1,19],16:[2,12],17:[2,12],18:[1,20],19:[1,21],21:[2,12],23:[2,12],29:22,30:[1,23],31:[1,24],32:[1,25],33:[1,26],34:[1,27],35:[1,28],36:[1,29]}],defaultActions:{11:[2,1]},parseError:function(t,e){throw new Error(t)},parse:function(t){var e=this,n=[0],r=[null],s=[],i=this.table,o="",l=0,h=0,a=0;this.lexer.setInput(t),this.lexer.yy=this.yy,this.yy.lexer=this.lexer,this.yy.parser=this,void 0===this.lexer.yylloc&&(this.lexer.yylloc={});var c=this.lexer.yylloc;s.push(c);var u=this.lexer.options&&this.lexer.options.ranges;function y(){var t;return"number"!=typeof(t=e.lexer.lex()||1)&&(t=e.symbols_[t]||t),t}"function"==typeof this.yy.parseError&&(this.parseError=this.yy.parseError);for(var f,p,g,m,d,b,x,_,w,k,$={};;){if(g=n[n.length-1],this.defaultActions[g]?m=this.defaultActions[g]:(null==f&&(f=y()),m=i[g]&&i[g][f]),void 0===m||!m.length||!m[0]){var S="";if(!a){for(b in w=[],i[g])this.terminals_[b]&&b>2&&w.push("'"+this.terminals_[b]+"'");S=this.lexer.showPosition?"Parse error on line "+(l+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+w.join(", ")+", got '"+(this.terminals_[f]||f)+"'":"Parse error on line "+(l+1)+": Unexpected "+(1==f?"end of input":"'"+(this.terminals_[f]||f)+"'"),this.parseError(S,{text:this.lexer.match,token:this.terminals_[f]||f,line:this.lexer.yylineno,loc:c,expected:w})}if(3==a){if(1==f)throw new Error(S||"Parsing halted.");h=this.lexer.yyleng,o=this.lexer.yytext,l=this.lexer.yylineno,c=this.lexer.yylloc,f=y()}for(;!(2..toString()in i[g]);){if(0===g)throw new Error(S||"Parsing halted.");k=1,n.length=n.length-2*k,r.length=r.length-k,s.length=s.length-k,g=n[n.length-1]}p=2==f?null:f,f=2,m=i[g=n[n.length-1]]&&i[g][2],a=3}if(m[0]instanceof Array&&m.length>1)throw new Error("Parse Error: multiple actions possible at state: "+g+", token: "+f);switch(m[0]){case 1:n.push(f),r.push(this.lexer.yytext),s.push(this.lexer.yylloc),n.push(m[1]),f=null,p?(f=p,p=null):(h=this.lexer.yyleng,o=this.lexer.yytext,l=this.lexer.yylineno,c=this.lexer.yylloc,a>0&&a--);break;case 2:if(x=this.productions_[m[1]][1],$.$=r[r.length-x],$._$={first_line:s[s.length-(x||1)].first_line,last_line:s[s.length-1].last_line,first_column:s[s.length-(x||1)].first_column,last_column:s[s.length-1].last_column},u&&($._$.range=[s[s.length-(x||1)].range[0],s[s.length-1].range[1]]),void 0!==(d=this.performAction.call($,o,h,l,this.yy,m[1],r,s)))return d;x&&(n=n.slice(0,-1*x*2),r=r.slice(0,-1*x),s=s.slice(0,-1*x)),n.push(this.productions_[m[1]][0]),r.push($.$),s.push($._$),_=i[n[n.length-2]][n[n.length-1]],n.push(_);break;case 3:return!0}}return!0}},e=function(){var t={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t){return this._input=t,this._more=this._less=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e-1),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var s=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[s[0],s[0]+this.yyleng-e]),this},more:function(){return this._more=!0,this},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},next:function(){if(this.done)return this.EOF;var t,e,n,r,s;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)||(e=n,r=o,this.options.flex));o++);return e?((s=e[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=s.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:s?s[s.length-1].length-s[s.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+e[0].length},this.yytext+=e[0],this.match+=e[0],this.matches=e,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._input=this._input.slice(e[0].length),this.matched+=e[0],t=this.performAction.call(this,this.yy,this,i[r],this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),t||void 0):""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return void 0!==t?t:this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.pop()},_currentRules:function(){return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules},topState:function(){return this.conditionStack[this.conditionStack.length-2]},pushState:function(t){this.begin(t)},options:{},performAction:function(t,e,n,r){switch(n){case 0:return"*";case 1:return"/";case 2:return"-";case 3:return"+";case 4:return"^";case 5:return"%";case 6:return"(";case 7:return")";case 8:return",";case 9:return"==";case 10:return"!=";case 11:return"~=";case 12:return">=";case 13:return"<=";case 14:return"<";case 15:return">";case 16:return"notIn";case 17:return"and";case 18:return"or";case 19:return"not";case 20:return"in";case 21:return"of";case 22:return"if";case 23:return"then";case 24:return"else";case 25:break;case 26:return"Number";case 27:return e.yytext=JSON.stringify({name:e.yytext,type:"unescaped"}),"Symbol";case 28:return e.yytext=JSON.stringify({name:t.buildString("'",e.yytext),type:"single-quoted"}),"Symbol";case 29:return e.yytext=JSON.stringify(t.buildString('"',e.yytext)),"String";case 30:return"EOF"}},rules:[/^(?:\*)/,/^(?:\/)/,/^(?:-)/,/^(?:\+)/,/^(?:\^)/,/^(?:\%)/,/^(?:\()/,/^(?:\))/,/^(?:\,)/,/^(?:==)/,/^(?:\!=)/,/^(?:\~=)/,/^(?:>=)/,/^(?:<=)/,/^(?:<)/,/^(?:>)/,/^(?:not\s+in[^\w])/,/^(?:and[^\w])/,/^(?:or[^\w])/,/^(?:not[^\w])/,/^(?:in[^\w])/,/^(?:of[^\w])/,/^(?:if[^\w])/,/^(?:then[^\w])/,/^(?:else[^\w])/,/^(?:\s+)/,/^(?:[0-9]+(?:\.[0-9]+)?(?![0-9\.]))/,/^(?:[a-zA-Z$_][\.a-zA-Z0-9$_]*)/,/^(?:'(?:\\'|\\\\|[^'\\])*')/,/^(?:"(?:\\"|\\\\|[^"\\])*")/,/^(?:$)/],conditions:{INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30],inclusive:!0}}};return t}();function n(){this.yy={}}return t.lexer=e,n.prototype=t,t.Parser=n,new n}();const n=e;e.Parser;class r extends ReferenceError{I18N_STRING="UNKNOWN_FUNCTION";constructor(t){super(`Unknown function: ${t}()`),this.functionName=t}}class s extends ReferenceError{I18N_STRING="UNKNOWN_PROPERTY";constructor(t){super(`Property “${t}” does not exist.`),this.propertyName=t}}class i extends TypeError{I18N_STRING="UNKNOWN_OPTION";constructor(t){super("Unknown option: "+t),this.keyName=t}}class o extends TypeError{I18N_STRING="UNEXPECTED_TYPE";constructor(t,e){super(`Expected a ${t}, but got a ${e} instead.`),this.expectedType=t,this.recievedType=e}}class l extends Error{I18N_STRING="INTERNAL";constructor(t){super(t)}}function h(t,e){return("object"==typeof t||"function"==typeof t)&&Object.prototype.hasOwnProperty.call(t,e)}function a(t,e){return(t%e+e)%e}function c(t){return Array.isArray(t)&&1===t.length&&(t=t[0]),function(t){return"object"!=typeof t?t:t instanceof Number||t instanceof String||t instanceof Boolean?t.valueOf():void 0}(t)}function u(t){return void 0===(t=c(t))?"undefined":null===t?"null":!0===t?"true":!1===t?"false":"number"==typeof t?"number":"string"==typeof t?"text":"object"!=typeof t&&"function"!=typeof t?"unknown type":Array.isArray(t)?"list":"object"}function y(t){if("number"==typeof(t=c(t)))return t;throw new o("number",u(t))}function f(t){if("string"==typeof(t=c(t)))return t;throw new o("text",u(t))}function p(t){if("string"==typeof(t=c(t))||"number"==typeof t)return t;throw new o("text or number",u(t))}function g(t){if(null==t)throw new o("list",u(t));return Array.isArray(t)?t:[t]}function m(t){const e=[...t],n=[];for(;e.length;){const t=e.pop();Array.isArray(t)?e.push(...t):n.push(t)}return n.reverse()}const d={isfn:(t,e)=>h(t,e)&&"function"==typeof t[e],unknown(t){throw new r(t)},coerceArray:g,coerceNumber:y,coerceNumberOrString:p,coerceBoolean:function(t){if("boolean"==typeof(t=c(t)))return t;throw new o("logical value (“true” or “false”)",u(t))},isSubset(t,e){const n=g(t),r=g(e);return n.every(t=>r.includes(t))},buildString(t,e){t=String(t)[0];let n="";if((e=String(e))[0]!==t||e[e.length-1]!==t)throw new l("Unexpected internal error: String literal doesn't begin/end with the right quotation mark.");for(let r=1;r=e.length-1)throw new l("Unexpected internal error: Unescaped backslash at the end of string literal.");if("\\"===e[r])n+="\\";else{if(e[r]!==t)throw new l("Unexpected internal error: Invalid escaped character in string literal: "+e[r]);n+=t}}else{if(e[r]===t)throw new l("Unexpected internal error: String literal contains unescaped quotation mark.");n+=e[r]}return n},reduceRelation(t){const e=[],n=[];let r=m([t[0]]).join(""),s=0;for(let i=1;i2)throw new TypeError("Too many arguments.");e="object"==typeof e?e:{};const o=["extraFunctions","constants","customProp","operators"];let{extraFunctions:l,constants:c,customProp:u,operators:g}=e;for(const t of Object.keys(e))if(!o.includes(t))throw new i(t);let b={abs:Math.abs,ceil:Math.ceil,floor:Math.floor,log:Math.log,log2:Math.log2,log10:Math.log10,max:Math.max,min:Math.min,round:Math.round,sqrt:Math.sqrt,exists:t=>null!=t,empty:t=>null==t||""===t||Array.isArray(t)&&0===t.length};if(l)for(const t of Object.keys(l))b[t]=l[t];let x={"+":(t,e)=>p(t)+p(e),"-":(t,e)=>void 0===e?-y(t):y(t)-y(e),"*":(t,e)=>y(t)*y(e),"/":(t,e)=>y(t)/y(e),"%":(t,e)=>a(y(t),y(e)),"^":(t,e)=>Math.pow(y(t),y(e)),"==":(t,e)=>t===e,"!=":(t,e)=>t!==e,"<":(t,e)=>y(t)y(t)<=y(e),">=":(t,e)=>y(t)>=y(e),">":(t,e)=>y(t)>y(e),"~=":(t,e)=>RegExp(f(e)).test(f(t))};if(g)for(const t of Object.keys(g))x[t]=g[t];g=x,c=c??{};let _=m(n.parse(t));function w(t,e){if(h(e??{},t))return e[t];throw new s(t)}function k(t){return function(e){if(h(t??{},e))return t[e];throw new s(e)}}function $(t){return function({name:e},...n){if(h(t,e)&&"function"==typeof t[e])return t[e](...n);throw new r(e)}}function S({name:t,type:e},n){return"unescaped"===e&&h(c,t)?c[t]:w(t,n)}_.unshift("return "),_.push(";"),"function"==typeof u&&(w=(t,e)=>u(t,k(e),e));let N=new Function("call","ops","std","prop","data",_.join(""));return function(t){try{return N($(b),g,d,S,t)}catch(t){return t}}},Object.defineProperty(t,"__esModule",{value:!0}),t}({}); \ No newline at end of file diff --git a/dist/cjs/filtrex.js b/dist/cjs/filtrex.js index e69865d..a045035 100644 --- a/dist/cjs/filtrex.js +++ b/dist/cjs/filtrex.js @@ -1663,13 +1663,19 @@ var _parser = (function() { case 26: return "Number"; case 27: - yy_.yytext = JSON.stringify(yy_.yytext); + yy_.yytext = JSON.stringify({ + name: yy_.yytext, + type: 'unescaped' + }); return "Symbol"; case 28: - yy_.yytext = yy.buildString("'", yy_.yytext); + yy_.yytext = JSON.stringify({ + name: yy.buildString("'", yy_.yytext), + type: 'single-quoted' + }); return "Symbol"; case 29: - yy_.yytext = yy.buildString('"', yy_.yytext); + yy_.yytext = JSON.stringify(yy.buildString('"', yy_.yytext)); return "String"; case 30: return "EOF"; @@ -1696,6 +1702,102 @@ var _parser = (function() { const parser = _parser; _parser.Parser; +/** + * Runtime error – user attempted to call a function + * which is not a predefined function, nor specified + * in `options.extraFunctions`. + * + * @prop {string} functionName + * @prop {string} I18N_STRING has the value `'UNKNOWN_FUNCTION'` + */ +class UnknownFunctionError +extends ReferenceError { + I18N_STRING = 'UNKNOWN_FUNCTION' + + constructor (funcName) { + super(`Unknown function: ${funcName}()`); + this.functionName = funcName; + } +} + +/** + * Runtime error – user attempted to access a property which + * is not present in the `data` object, nor in the `constants`. + * If the property is meant to be empty, use `undefined` or + * `null` as its value. If you need to use optional properties + * in your `data`, define a `customProp` function. + * + * @prop {string} propertyName + * @prop {string} I18N_STRING has the value `'UNKNOWN_PROPERTY'` + */ +class UnknownPropertyError +extends ReferenceError { + I18N_STRING = 'UNKNOWN_PROPERTY' + + constructor (propName) { + super(`Property “${propName}” does not exist.`); + this.propertyName = propName; + } +} + +/** + * Compile time error – you specified an option which + * was not recognized by Filtrex. Double-check your + * spelling and the version of Filtrex you are using. + * + * @prop {string} keyName + * @prop {string} I18N_STRING has the value `'UNKNOWN_OPTION'` + */ +class UnknownOptionError +extends TypeError { + I18N_STRING = 'UNKNOWN_OPTION' + + constructor (key) { + super(`Unknown option: ${key}`); + this.keyName = key; + } +} + +/** + * Runtime error – user passed a different type than the one + * accepted by the function or operator. + * + * The possible values of `expectedType` and `recievedType` + * are: `"undefined"`, `"null"`, `"true"`, `"false"`, `"number"`, + * `"text"`, `"unknown type"`, `"list"`, `"object"`, `"text or number"` + * and `"logical value (“true” or “false”)"` + * + * @prop {string} expectedType + * @prop {string} recievedType + * @prop {string} I18N_STRING has the value `'UNEXPECTED_TYPE'` + */ +class UnexpectedTypeError +extends TypeError { + I18N_STRING = 'UNEXPECTED_TYPE' + + constructor (expected, got) { + super(`Expected a ${expected}, but got a ${got} instead.`); + + this.expectedType = expected; + this.recievedType = got; + } +} + +/** + * An internal error. This was not meant to happen, please report + * at https://github.com/m93a/filtrex/ + * + * @prop {string} I18N_STRING has the value `'INTERNAL'` + */ +class InternalError +extends Error { + I18N_STRING = 'INTERNAL' + + constructor (message) { + super(message); + } +} + /** * Determines whether an object has a property with the specified name. * @param {object} obj the object to be checked @@ -1775,33 +1877,33 @@ function num(value) { value = unwrap(value); if (typeof value === 'number') return value - throw new TypeError(`Expected a number, but got a ${prettyType(value)} instead.`) + throw new UnexpectedTypeError('number', prettyType(value)) } function str(value) { value = unwrap(value); if (typeof value === 'string') return value - throw new TypeError(`Expected a text, but got a ${prettyType(value)} instead.`) + throw new UnexpectedTypeError('text', prettyType(value)) } function numstr(value) { value = unwrap(value); if (typeof value === 'string' || typeof value === 'number') return value - throw new TypeError(`Expected a text or a number, but got a ${prettyType(value)} instead.`) + throw new UnexpectedTypeError('text or number', prettyType(value)) } function bool(value) { value = unwrap(value); if (typeof value === 'boolean') return value - throw new TypeError(`Expected a logical value (“true” or “false”), but got a ${prettyType(value)} instead.`) + throw new UnexpectedTypeError('logical value (“true” or “false”)', prettyType(value)) } function arr(value) { if (value === undefined || value === null) { - throw new TypeError(`Expected a list, but got ${value} instead.`) + throw new UnexpectedTypeError('list', prettyType(value)) } if (Array.isArray(value)) { @@ -1842,7 +1944,7 @@ const std = }, unknown(funcName) { - throw new ReferenceError('Unknown function: ' + funcName + '()') + throw new UnknownFunctionError(funcName) }, coerceArray: arr, @@ -1863,22 +1965,22 @@ const std = let built = ''; if (literal[0] !== quote || literal[literal.length-1] !== quote) - throw new Error(`Unexpected internal error: String literal doesn't begin/end with the right quotation mark.`) + throw new InternalError(`Unexpected internal error: String literal doesn't begin/end with the right quotation mark.`) for (let i = 1; i < literal.length - 1; i++) { if (literal[i] === "\\") { i++; - if (i >= literal.length - 1) throw new Error(`Unexpected internal error: Unescaped backslash at the end of string literal.`) + if (i >= literal.length - 1) throw new InternalError(`Unexpected internal error: Unescaped backslash at the end of string literal.`) if (literal[i] === "\\") built += '\\'; else if (literal[i] === quote) built += quote; - else throw new Error(`Unexpected internal error: Invalid escaped character in string literal: ${literal[i]}`) + else throw new InternalError(`Unexpected internal error: Invalid escaped character in string literal: ${literal[i]}`) } else if (literal[i] === quote) { - throw new Error(`Unexpected internal error: String literal contains unescaped quotation mark.`) + throw new InternalError(`Unexpected internal error: String literal contains unescaped quotation mark.`) } else { @@ -1886,7 +1988,7 @@ const std = } } - return JSON.stringify(built) + return built }, reduceRelation(arr) { @@ -1913,15 +2015,59 @@ const std = parser.yy = Object.create(std); + + /** - * Filtrex provides compileExpression() to compile user expressions to JavaScript. + * A simple, safe, JavaScript expression engine, allowing end-users to enter arbitrary expressions without p0wning you. + * + * @example + * // Input from user (e.g. search filter) + * let expression = 'transactions <= 5 and abs(profit) > 20.5'; * - * See https://github.com/joewalnes/filtrex for tutorial, reference and examples. - * MIT License. + * // Compile expression to executable function + * let myfilter = compileExpression(expression); * - * Includes Jison by Zachary Carter. See http://jison.org/ + * // Execute function + * myfilter({transactions: 3, profit:-40.5}); // returns 1 + * myfilter({transactions: 3, profit:-14.5}); // returns 0 * - * -Joe Walnes + * @param expression + * The expression to be parsed. Under the hood, the expression gets compiled to a clean and fast JavaScript function. + * There are only 2 types: numbers and strings. Numbers may be floating point or integers. Boolean logic is applied + * on the truthy value of values (e.g. any non-zero number is true, any non-empty string is true, otherwise false). + * Examples of numbers: `43`, `-1.234`; example of a string: `"hello"`; example of external data variable: `foo`, `a.b.c`, + * `'foo-bar'`. + * You can use the following operations: + * * `x + y` Add + * * `x - y` Subtract + * * `x * y` Multiply + * * `x / y` Divide + * * `x % y` Modulo + * * `x ^ y` Power + * * `x == y` Equals + * * `x < y` Less than + * * `x <= y` Less than or equal to + * * `x > y` Greater than + * * `x >= y` Greater than or equal to + * * `x == y <= z` Chained relation, equivalent to `(x == y and y <= z)` + * * `x of y` Get property x of object y + * * `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)` + * * `x or y` Boolean or + * * `x and y` Boolean and + * * `not x` Boolean not + * * `if x then y else z` If boolean x, value y, else z + * * `( x )` Explicity operator precedence + * * `( x, y, z )` Array of elements x, y and z + * * `abs(x)` Absolute value + * * `ceil(x)` Round floating point up + * * `floor(x)` Round floating point down + * * `log(x)` Natural logarithm + * * `max(a, b, c...)` Max value (variable length of args) + * * `min(a, b, c...)` Min value (variable length of args) + * * `round(x)` Round floating point + * * `sqrt(x)` Square root + * * `myFooBarFunction(x)` Custom function defined in `options.extraFunctions` */ function compileExpression(expression, options) { @@ -1930,12 +2076,12 @@ function compileExpression(expression, options) { if (arguments.length > 2) throw new TypeError('Too many arguments.') options = typeof options === "object" ? options : {}; - let {extraFunctions, customProp, operators} = options; + + const knownOptions = ['extraFunctions', 'constants', 'customProp', 'operators']; + let {extraFunctions, constants, customProp, operators} = options; + for (const key of Object.keys(options)) - { - if (!(["extraFunctions", "customProp", "operators"].includes(key))) - throw new TypeError(`Unknown option: ${key}`) - } + if (!knownOptions.includes(key)) throw new UnknownOptionError(key) @@ -1946,9 +2092,10 @@ function compileExpression(expression, options) { ceil: Math.ceil, floor: Math.floor, log: Math.log, + log2: Math.log2, + log10: Math.log10, max: Math.max, min: Math.min, - random: Math.random, round: Math.round, sqrt: Math.sqrt, exists: (v) => v !== undefined && v !== null, @@ -1989,6 +2136,8 @@ function compileExpression(expression, options) { operators = defaultOperators; + constants = constants ?? {}; + // Compile the expression @@ -2000,35 +2149,42 @@ function compileExpression(expression, options) { // Metaprogramming functions - function prop(name, obj) { - if (hasOwnProperty(obj||{}, name)) + function nakedProp(name, obj) { + if (hasOwnProperty(obj ?? {}, name)) return obj[name] - throw new ReferenceError(`Property “${name}” does not exist.`) + throw new UnknownPropertyError(name) } function safeGetter(obj) { return function get(name) { - if (hasOwnProperty(obj||{}, name)) + if (hasOwnProperty(obj ?? {}, name)) return obj[name] - throw new ReferenceError(`Property “${name}” does not exist.`) + throw new UnknownPropertyError(name) } } if (typeof customProp === 'function') { - prop = (name, obj) => customProp(name, safeGetter(obj), obj); + nakedProp = (name, obj) => customProp(name, safeGetter(obj), obj); } function createCall(fns) { - return function call(name, ...args) { + return function call({ name }, ...args) { if (hasOwnProperty(fns, name) && typeof fns[name] === "function") return fns[name](...args) - throw new ReferenceError(`Unknown function: ${name}()`) + throw new UnknownFunctionError(name) } } + function prop({ name, type }, obj) { + if (type === 'unescaped' && hasOwnProperty(constants, name)) + return constants[name] + + return nakedProp(name, obj) + } + // Patch together and return diff --git a/dist/esm/errors.mjs b/dist/esm/errors.mjs new file mode 100644 index 0000000..277a220 --- /dev/null +++ b/dist/esm/errors.mjs @@ -0,0 +1,95 @@ +/** + * Runtime error – user attempted to call a function + * which is not a predefined function, nor specified + * in `options.extraFunctions`. + * + * @prop {string} functionName + * @prop {string} I18N_STRING has the value `'UNKNOWN_FUNCTION'` + */ +export class UnknownFunctionError +extends ReferenceError { + I18N_STRING = 'UNKNOWN_FUNCTION' + + constructor (funcName) { + super(`Unknown function: ${funcName}()`) + this.functionName = funcName + } +} + +/** + * Runtime error – user attempted to access a property which + * is not present in the `data` object, nor in the `constants`. + * If the property is meant to be empty, use `undefined` or + * `null` as its value. If you need to use optional properties + * in your `data`, define a `customProp` function. + * + * @prop {string} propertyName + * @prop {string} I18N_STRING has the value `'UNKNOWN_PROPERTY'` + */ +export class UnknownPropertyError +extends ReferenceError { + I18N_STRING = 'UNKNOWN_PROPERTY' + + constructor (propName) { + super(`Property “${propName}” does not exist.`) + this.propertyName = propName + } +} + +/** + * Compile time error – you specified an option which + * was not recognized by Filtrex. Double-check your + * spelling and the version of Filtrex you are using. + * + * @prop {string} keyName + * @prop {string} I18N_STRING has the value `'UNKNOWN_OPTION'` + */ +export class UnknownOptionError +extends TypeError { + I18N_STRING = 'UNKNOWN_OPTION' + + constructor (key) { + super(`Unknown option: ${key}`) + this.keyName = key + } +} + +/** + * Runtime error – user passed a different type than the one + * accepted by the function or operator. + * + * The possible values of `expectedType` and `recievedType` + * are: `"undefined"`, `"null"`, `"true"`, `"false"`, `"number"`, + * `"text"`, `"unknown type"`, `"list"`, `"object"`, `"text or number"` + * and `"logical value (“true” or “false”)"` + * + * @prop {string} expectedType + * @prop {string} recievedType + * @prop {string} I18N_STRING has the value `'UNEXPECTED_TYPE'` + */ +export class UnexpectedTypeError +extends TypeError { + I18N_STRING = 'UNEXPECTED_TYPE' + + constructor (expected, got) { + super(`Expected a ${expected}, but got a ${got} instead.`) + + this.expectedType = expected + this.recievedType = got + } +} + +/** + * An internal error. This was not meant to happen, please report + * at https://github.com/m93a/filtrex/ + * + * @prop {string} I18N_STRING has the value `'INTERNAL'` + */ +export class InternalError +extends Error { + I18N_STRING = 'INTERNAL' + + constructor (message) { + super(message) + } +} diff --git a/dist/esm/filtrex.d.ts b/dist/esm/filtrex.d.ts index 0c3be49..d88bda4 100644 --- a/dist/esm/filtrex.d.ts +++ b/dist/esm/filtrex.d.ts @@ -18,7 +18,7 @@ * on the truthy value of values (e.g. any non-zero number is true, any non-empty string is true, otherwise false). * Examples of numbers: `43`, `-1.234`; example of a string: `"hello"`; example of external data variable: `foo`, `a.b.c`, * `'foo-bar'`. - * You can use the following operators: + * You can use the following operations: * * `x + y` Add * * `x - y` Subtract * * `x * y` Multiply @@ -30,6 +30,7 @@ * * `x <= y` Less than or equal to * * `x > y` Greater than * * `x >= y` Greater than or equal to + * * `x == y <= z` Chained relation, equivalent to `(x == y and y <= z)` * * `x of y` Get property x of object y * * `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)` @@ -45,7 +46,6 @@ * * `log(x)` Natural logarithm * * `max(a, b, c...)` Max value (variable length of args) * * `min(a, b, c...)` Min value (variable length of args) - * * `random()` Random floating point from 0.0 to 1.0 * * `round(x)` Round floating point * * `sqrt(x)` Square root * * `myFooBarFunction(x)` Custom function defined in `options.extraFunctions` @@ -66,6 +66,16 @@ export interface Options [T: string]: Function } + /** + * Pass constants like `pi` or `true` to the expression without having to modify data. + * These constants will shadow identically named properties on the data object. In order + * to access `data.pi` instead of `constants.pi`, for example, use a single-quoted + * symbol in your expression, ie. `'pi'` instead of just `pi`. + */ + constants?: { + [T: string]: any + } + /** * If you want to do some more magic with your expression, you can supply a custom function * that will resolve the identifiers used in the expression and assign them a value yourself. diff --git a/dist/esm/filtrex.mjs b/dist/esm/filtrex.mjs index d52427d..d8bf9e1 100644 --- a/dist/esm/filtrex.mjs +++ b/dist/esm/filtrex.mjs @@ -1,6 +1,7 @@ // the parser is dynamically generated from generateParser.js at compile time import { parser } from './parser.mjs' import { hasOwnProperty, bool, num, numstr, mod, arr, str, flatten, code } from './utils.mjs' +import { UnknownFunctionError, UnknownPropertyError, UnknownOptionError, InternalError } from './errors.mjs' // Shared utility functions const std = @@ -11,7 +12,7 @@ const std = }, unknown(funcName) { - throw new ReferenceError('Unknown function: ' + funcName + '()') + throw new UnknownFunctionError(funcName) }, coerceArray: arr, @@ -32,22 +33,22 @@ const std = let built = '' if (literal[0] !== quote || literal[literal.length-1] !== quote) - throw new Error(`Unexpected internal error: String literal doesn't begin/end with the right quotation mark.`) + throw new InternalError(`Unexpected internal error: String literal doesn't begin/end with the right quotation mark.`) for (let i = 1; i < literal.length - 1; i++) { if (literal[i] === "\\") { i++; - if (i >= literal.length - 1) throw new Error(`Unexpected internal error: Unescaped backslash at the end of string literal.`) + if (i >= literal.length - 1) throw new InternalError(`Unexpected internal error: Unescaped backslash at the end of string literal.`) if (literal[i] === "\\") built += '\\' else if (literal[i] === quote) built += quote - else throw new Error(`Unexpected internal error: Invalid escaped character in string literal: ${literal[i]}`) + else throw new InternalError(`Unexpected internal error: Invalid escaped character in string literal: ${literal[i]}`) } else if (literal[i] === quote) { - throw new Error(`Unexpected internal error: String literal contains unescaped quotation mark.`) + throw new InternalError(`Unexpected internal error: String literal contains unescaped quotation mark.`) } else { @@ -55,7 +56,7 @@ const std = } } - return JSON.stringify(built) + return built }, reduceRelation(arr) { @@ -82,15 +83,59 @@ const std = parser.yy = Object.create(std) + + /** - * Filtrex provides compileExpression() to compile user expressions to JavaScript. + * A simple, safe, JavaScript expression engine, allowing end-users to enter arbitrary expressions without p0wning you. + * + * @example + * // Input from user (e.g. search filter) + * let expression = 'transactions <= 5 and abs(profit) > 20.5'; * - * See https://github.com/joewalnes/filtrex for tutorial, reference and examples. - * MIT License. + * // Compile expression to executable function + * let myfilter = compileExpression(expression); * - * Includes Jison by Zachary Carter. See http://jison.org/ + * // Execute function + * myfilter({transactions: 3, profit:-40.5}); // returns 1 + * myfilter({transactions: 3, profit:-14.5}); // returns 0 * - * -Joe Walnes + * @param expression + * The expression to be parsed. Under the hood, the expression gets compiled to a clean and fast JavaScript function. + * There are only 2 types: numbers and strings. Numbers may be floating point or integers. Boolean logic is applied + * on the truthy value of values (e.g. any non-zero number is true, any non-empty string is true, otherwise false). + * Examples of numbers: `43`, `-1.234`; example of a string: `"hello"`; example of external data variable: `foo`, `a.b.c`, + * `'foo-bar'`. + * You can use the following operations: + * * `x + y` Add + * * `x - y` Subtract + * * `x * y` Multiply + * * `x / y` Divide + * * `x % y` Modulo + * * `x ^ y` Power + * * `x == y` Equals + * * `x < y` Less than + * * `x <= y` Less than or equal to + * * `x > y` Greater than + * * `x >= y` Greater than or equal to + * * `x == y <= z` Chained relation, equivalent to `(x == y and y <= z)` + * * `x of y` Get property x of object y + * * `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)` + * * `x or y` Boolean or + * * `x and y` Boolean and + * * `not x` Boolean not + * * `if x then y else z` If boolean x, value y, else z + * * `( x )` Explicity operator precedence + * * `( x, y, z )` Array of elements x, y and z + * * `abs(x)` Absolute value + * * `ceil(x)` Round floating point up + * * `floor(x)` Round floating point down + * * `log(x)` Natural logarithm + * * `max(a, b, c...)` Max value (variable length of args) + * * `min(a, b, c...)` Min value (variable length of args) + * * `round(x)` Round floating point + * * `sqrt(x)` Square root + * * `myFooBarFunction(x)` Custom function defined in `options.extraFunctions` */ export function compileExpression(expression, options) { @@ -99,12 +144,12 @@ export function compileExpression(expression, options) { if (arguments.length > 2) throw new TypeError('Too many arguments.') options = typeof options === "object" ? options : {} - let {extraFunctions, customProp, operators} = options + + const knownOptions = ['extraFunctions', 'constants', 'customProp', 'operators'] + let {extraFunctions, constants, customProp, operators} = options + for (const key of Object.keys(options)) - { - if (!(["extraFunctions", "customProp", "operators"].includes(key))) - throw new TypeError(`Unknown option: ${key}`) - } + if (!knownOptions.includes(key)) throw new UnknownOptionError(key) @@ -115,9 +160,10 @@ export function compileExpression(expression, options) { ceil: Math.ceil, floor: Math.floor, log: Math.log, + log2: Math.log2, + log10: Math.log10, max: Math.max, min: Math.min, - random: Math.random, round: Math.round, sqrt: Math.sqrt, exists: (v) => v !== undefined && v !== null, @@ -158,6 +204,8 @@ export function compileExpression(expression, options) { operators = defaultOperators + constants = constants ?? {} + // Compile the expression @@ -169,35 +217,42 @@ export function compileExpression(expression, options) { // Metaprogramming functions - function prop(name, obj) { - if (hasOwnProperty(obj||{}, name)) + function nakedProp(name, obj) { + if (hasOwnProperty(obj ?? {}, name)) return obj[name] - throw new ReferenceError(`Property “${name}” does not exist.`) + throw new UnknownPropertyError(name) } function safeGetter(obj) { return function get(name) { - if (hasOwnProperty(obj||{}, name)) + if (hasOwnProperty(obj ?? {}, name)) return obj[name] - throw new ReferenceError(`Property “${name}” does not exist.`) + throw new UnknownPropertyError(name) } } if (typeof customProp === 'function') { - prop = (name, obj) => customProp(name, safeGetter(obj), obj) + nakedProp = (name, obj) => customProp(name, safeGetter(obj), obj) } function createCall(fns) { - return function call(name, ...args) { + return function call({ name }, ...args) { if (hasOwnProperty(fns, name) && typeof fns[name] === "function") return fns[name](...args) - throw new ReferenceError(`Unknown function: ${name}()`) + throw new UnknownFunctionError(name) } } + function prop({ name, type }, obj) { + if (type === 'unescaped' && hasOwnProperty(constants, name)) + return constants[name] + + return nakedProp(name, obj) + } + // Patch together and return diff --git a/dist/esm/generateParser.mjs b/dist/esm/generateParser.mjs index e6c06b5..0c0bccb 100644 --- a/dist/esm/generateParser.mjs +++ b/dist/esm/generateParser.mjs @@ -40,17 +40,23 @@ const grammar = { [_`[0-9]+(?:\.[0-9]+)?(?![0-9\.])`, `return "Number";`], // 212.321 [_`[a-zA-Z$_][\.a-zA-Z0-9$_]*`, - `yytext = JSON.stringify(yytext); + `yytext = JSON.stringify({ + name: yytext, + type: 'unescaped' + }); return "Symbol";` ], // some.Symbol22 [_`'(?:\\'|\\\\|[^'\\])*'`, - `yytext = yy.buildString("'", yytext); + `yytext = JSON.stringify({ + name: yy.buildString("'", yytext), + type: 'single-quoted' + }); return "Symbol";` ], // 'any \'escaped\' symbol' [_`"(?:\\"|\\\\|[^"\\])*"`, - `yytext = yy.buildString('"', yytext); + `yytext = JSON.stringify(yy.buildString('"', yytext)); return "String";` ], // "any \"escaped\" string" diff --git a/dist/esm/parser.mjs b/dist/esm/parser.mjs index 543e2c0..2667416 100644 --- a/dist/esm/parser.mjs +++ b/dist/esm/parser.mjs @@ -1691,15 +1691,21 @@ var _parser = (function() { return "Number"; break; case 27: - yy_.yytext = JSON.stringify(yy_.yytext); + yy_.yytext = JSON.stringify({ + name: yy_.yytext, + type: 'unescaped' + }); return "Symbol"; break; case 28: - yy_.yytext = yy.buildString("'", yy_.yytext); + yy_.yytext = JSON.stringify({ + name: yy.buildString("'", yy_.yytext), + type: 'single-quoted' + }); return "Symbol"; break; case 29: - yy_.yytext = yy.buildString('"', yy_.yytext); + yy_.yytext = JSON.stringify(yy.buildString('"', yy_.yytext)); return "String"; break; case 30: diff --git a/dist/esm/utils.mjs b/dist/esm/utils.mjs index 152f603..759e3f4 100644 --- a/dist/esm/utils.mjs +++ b/dist/esm/utils.mjs @@ -1,3 +1,5 @@ +import { UnexpectedTypeError } from './errors.mjs' + /** * Determines whether an object has a property with the specified name. * @param {object} obj the object to be checked @@ -77,33 +79,33 @@ export function num(value) { value = unwrap(value) if (typeof value === 'number') return value - throw new TypeError(`Expected a number, but got a ${prettyType(value)} instead.`) + throw new UnexpectedTypeError('number', prettyType(value)) } export function str(value) { value = unwrap(value) if (typeof value === 'string') return value - throw new TypeError(`Expected a text, but got a ${prettyType(value)} instead.`) + throw new UnexpectedTypeError('text', prettyType(value)) } export function numstr(value) { value = unwrap(value) if (typeof value === 'string' || typeof value === 'number') return value - throw new TypeError(`Expected a text or a number, but got a ${prettyType(value)} instead.`) + throw new UnexpectedTypeError('text or number', prettyType(value)) } export function bool(value) { value = unwrap(value) if (typeof value === 'boolean') return value - throw new TypeError(`Expected a logical value (“true” or “false”), but got a ${prettyType(value)} instead.`) + throw new UnexpectedTypeError('logical value (“true” or “false”)', prettyType(value)) } export function arr(value) { if (value === undefined || value === null) { - throw new TypeError(`Expected a list, but got ${value} instead.`) + throw new UnexpectedTypeError('list', prettyType(value)) } if (Array.isArray(value)) { diff --git a/src/filtrex.d.ts b/src/filtrex.d.ts index 88a9b76..d88bda4 100644 --- a/src/filtrex.d.ts +++ b/src/filtrex.d.ts @@ -18,7 +18,7 @@ * on the truthy value of values (e.g. any non-zero number is true, any non-empty string is true, otherwise false). * Examples of numbers: `43`, `-1.234`; example of a string: `"hello"`; example of external data variable: `foo`, `a.b.c`, * `'foo-bar'`. - * You can use the following operators: + * You can use the following operations: * * `x + y` Add * * `x - y` Subtract * * `x * y` Multiply @@ -30,6 +30,7 @@ * * `x <= y` Less than or equal to * * `x > y` Greater than * * `x >= y` Greater than or equal to + * * `x == y <= z` Chained relation, equivalent to `(x == y and y <= z)` * * `x of y` Get property x of object y * * `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)` @@ -45,7 +46,6 @@ * * `log(x)` Natural logarithm * * `max(a, b, c...)` Max value (variable length of args) * * `min(a, b, c...)` Min value (variable length of args) - * * `random()` Random floating point from 0.0 to 1.0 * * `round(x)` Round floating point * * `sqrt(x)` Square root * * `myFooBarFunction(x)` Custom function defined in `options.extraFunctions` diff --git a/src/filtrex.mjs b/src/filtrex.mjs index 55f7afe..d8bf9e1 100644 --- a/src/filtrex.mjs +++ b/src/filtrex.mjs @@ -83,15 +83,59 @@ const std = parser.yy = Object.create(std) + + /** - * Filtrex provides compileExpression() to compile user expressions to JavaScript. + * A simple, safe, JavaScript expression engine, allowing end-users to enter arbitrary expressions without p0wning you. + * + * @example + * // Input from user (e.g. search filter) + * let expression = 'transactions <= 5 and abs(profit) > 20.5'; * - * See https://github.com/joewalnes/filtrex for tutorial, reference and examples. - * MIT License. + * // Compile expression to executable function + * let myfilter = compileExpression(expression); * - * Includes Jison by Zachary Carter. See http://jison.org/ + * // Execute function + * myfilter({transactions: 3, profit:-40.5}); // returns 1 + * myfilter({transactions: 3, profit:-14.5}); // returns 0 * - * -Joe Walnes + * @param expression + * The expression to be parsed. Under the hood, the expression gets compiled to a clean and fast JavaScript function. + * There are only 2 types: numbers and strings. Numbers may be floating point or integers. Boolean logic is applied + * on the truthy value of values (e.g. any non-zero number is true, any non-empty string is true, otherwise false). + * Examples of numbers: `43`, `-1.234`; example of a string: `"hello"`; example of external data variable: `foo`, `a.b.c`, + * `'foo-bar'`. + * You can use the following operations: + * * `x + y` Add + * * `x - y` Subtract + * * `x * y` Multiply + * * `x / y` Divide + * * `x % y` Modulo + * * `x ^ y` Power + * * `x == y` Equals + * * `x < y` Less than + * * `x <= y` Less than or equal to + * * `x > y` Greater than + * * `x >= y` Greater than or equal to + * * `x == y <= z` Chained relation, equivalent to `(x == y and y <= z)` + * * `x of y` Get property x of object y + * * `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)` + * * `x or y` Boolean or + * * `x and y` Boolean and + * * `not x` Boolean not + * * `if x then y else z` If boolean x, value y, else z + * * `( x )` Explicity operator precedence + * * `( x, y, z )` Array of elements x, y and z + * * `abs(x)` Absolute value + * * `ceil(x)` Round floating point up + * * `floor(x)` Round floating point down + * * `log(x)` Natural logarithm + * * `max(a, b, c...)` Max value (variable length of args) + * * `min(a, b, c...)` Min value (variable length of args) + * * `round(x)` Round floating point + * * `sqrt(x)` Square root + * * `myFooBarFunction(x)` Custom function defined in `options.extraFunctions` */ export function compileExpression(expression, options) { diff --git a/test/arithmetics.js b/test/arithmetics.js index 70e5a8b..de9f988 100644 --- a/test/arithmetics.js +++ b/test/arithmetics.js @@ -36,7 +36,7 @@ describe('Arithmetics', () => { it('supports functions with multiple args', () => { - expect( eval('random() >= 0') ).equals(true); + expect( eval('min()') ).equals(Infinity); expect( eval('min(2)') ).equals(2); expect( eval('max(2)') ).equals(2); expect( eval('min(2, 5)') ).equals(2);