Skip to content

Commit

Permalink
added operator overloading (#30) and mathematically correct modulo (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
cshaa committed Jun 17, 2021
1 parent 3730609 commit a2c0397
Show file tree
Hide file tree
Showing 11 changed files with 704 additions and 280 deletions.
226 changes: 160 additions & 66 deletions dist/browser/filtrex.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,58 +118,58 @@ var filtrex = (function (exports) {
case 1:
return $$[$0 - 1];
case 2:
this.$ = ["(", "", $$[$0 - 2], " + ", $$[$0], "", ")"];
this.$ = ["(", "ops['", $$[$0 - 1], "'](", $$[$0 - 2], ", ", $$[$0], ")", ")"];
break;
case 3:
this.$ = ["(", "", $$[$0 - 2], " - ", $$[$0], "", ")"];
this.$ = ["(", "ops['", $$[$0 - 1], "'](", $$[$0 - 2], ", ", $$[$0], ")", ")"];
break;
case 4:
this.$ = ["(", "", $$[$0 - 2], " * ", $$[$0], "", ")"];
this.$ = ["(", "ops['", $$[$0 - 1], "'](", $$[$0 - 2], ", ", $$[$0], ")", ")"];
break;
case 5:
this.$ = ["(", "", $$[$0 - 2], " / ", $$[$0], "", ")"];
this.$ = ["(", "ops['", $$[$0 - 1], "'](", $$[$0 - 2], ", ", $$[$0], ")", ")"];
break;
case 6:
this.$ = ["(", "", $$[$0 - 2], " % ", $$[$0], "", ")"];
this.$ = ["(", "ops['", $$[$0 - 1], "'](", $$[$0 - 2], ", ", $$[$0], ")", ")"];
break;
case 7:
this.$ = ["(", "Math.pow( ", $$[$0 - 2], ", ", $$[$0], " )", ")"];
this.$ = ["(", "ops['", $$[$0 - 1], "'](", $$[$0 - 2], ", ", $$[$0], ")", ")"];
break;
case 8:
this.$ = ["(", "- ", $$[$0], "", ")"];
break;
case 9:
this.$ = ["(", "", "std.coerceBoolean", "(", $$[$0 - 2], ") && ", "std.coerceBoolean", "(", $$[$0], ")", ")"];
this.$ = ["(", "", "std.coerceBoolean", "", $$[$0 - 2], " && ", "std.coerceBoolean", "", $$[$0], "", ")"];
break;
case 10:
this.$ = ["(", "", "std.coerceBoolean", "(", $$[$0 - 2], ") || ", "std.coerceBoolean", "(", $$[$0], ")", ")"];
this.$ = ["(", "", "std.coerceBoolean", "", $$[$0 - 2], " || ", "std.coerceBoolean", "", $$[$0], "", ")"];
break;
case 11:
this.$ = ["(", "! ", "std.coerceBoolean", "(", $$[$0], ")", ")"];
this.$ = ["(", "! ", "std.coerceBoolean", "", $$[$0], "", ")"];
break;
case 12:
this.$ = ["(", "", $$[$0 - 2], " === ", $$[$0], "", ")"];
this.$ = ["(", "ops['", $$[$0 - 1], "'](", $$[$0 - 2], ", ", $$[$0], ")", ")"];
break;
case 13:
this.$ = ["(", "", $$[$0 - 2], " !== ", $$[$0], "", ")"];
this.$ = ["(", "ops['", $$[$0 - 1], "'](", $$[$0 - 2], ", ", $$[$0], ")", ")"];
break;
case 14:
this.$ = ["(", "RegExp(", $$[$0], ").test(", $$[$0 - 2], ")", ")"];
this.$ = ["(", "ops['", $$[$0 - 1], "'](", $$[$0 - 2], ", ", $$[$0], ")", ")"];
break;
case 15:
this.$ = ["(", "", $$[$0 - 2], " < ", $$[$0], "", ")"];
this.$ = ["(", "ops['", $$[$0 - 1], "'](", $$[$0 - 2], ", ", $$[$0], ")", ")"];
break;
case 16:
this.$ = ["(", "", $$[$0 - 2], " <= ", $$[$0], "", ")"];
this.$ = ["(", "ops['", $$[$0 - 1], "'](", $$[$0 - 2], ", ", $$[$0], ")", ")"];
break;
case 17:
this.$ = ["(", "", $$[$0 - 2], " > ", $$[$0], "", ")"];
this.$ = ["(", "ops['", $$[$0 - 1], "'](", $$[$0 - 2], ", ", $$[$0], ")", ")"];
break;
case 18:
this.$ = ["(", "", $$[$0 - 2], " >= ", $$[$0], "", ")"];
this.$ = ["(", "ops['", $$[$0 - 1], "'](", $$[$0 - 2], ", ", $$[$0], ")", ")"];
break;
case 19:
this.$ = ["(", "", "std.coerceBoolean", "(", $$[$0 - 4], ") ? ", $$[$0 - 2], " : ", $$[$0], "", ")"];
this.$ = ["(", "", "std.coerceBoolean", "", $$[$0 - 4], " ? ", $$[$0 - 2], " : ", $$[$0], "", ")"];
break;
case 20:
this.$ = ["(", "", $$[$0 - 1], "", ")"];
Expand Down Expand Up @@ -1793,63 +1793,130 @@ var filtrex = (function (exports) {
}
}

// the parser is dynamically generated from generateParser.js at compile time

// Shared utility functions
const std =
{
/**
* Mathematically correct modulo
* @param {number} a
* @param {number} b
* @returns {number}
*/

isfn: function(fns, funcName) {
return hasOwnProperty(fns, funcName) && typeof fns[funcName] === "function";
},
function mod(a, b) {
return (a % b + b) % b
}

unknown: function(funcName) {
throw new ReferenceError('Unknown function: ' + funcName + '()');
},

coerceArray: function(value) {
if (value === undefined || value === null) {
throw new TypeError(`Expected a list, but got ${value} instead.`)
}

if (Array.isArray(value)) {
return value;
} else {
return [value];
}
},
// Type assertions/coertions

coerceNumber: function (value) {
const origValue = value;
function num(value) {
const origValue = value;

if (value === undefined || value === null)
throw new TypeError(`Expected a numeric value, but got ${value} instead.`)
if (value === undefined || value === null)
throw new TypeError(`Expected a numeric value, but got ${value} instead.`)

if (Array.isArray(value) && value.length === 1)
value = value[0];
if (Array.isArray(value) && value.length === 1)
value = value[0];

if (typeof value === 'object')
value = toPrimitive(value);
if (typeof value === 'object')
value = toPrimitive(value, 'number');

if (typeof value === 'number' || typeof value === 'bigint')
return value;
if (typeof value === 'number')
return value;

throw new TypeError(`Expected a numeric value, but got an ${typeof origValue} instead.`)
},
throw new TypeError(`Expected a numeric value, but got an ${typeof origValue} instead.`)
}

function str(value) {
const origValue = value;

if (value === undefined || value === null)
throw new TypeError(`Expected a text, but got ${value} instead.`)

if (Array.isArray(value) && value.length === 1)
value = value[0];

if (typeof value === 'object')
value = toPrimitive(value, 'string');

if (typeof value === 'string')
return value;

throw new TypeError(`Expected a text, but got an ${typeof origValue} instead.`)
}

function numstr(value) {
const origValue = value;
let converted;

if (typeof value === 'string' || typeof value === 'number')
return value

if (value === undefined || value === null)
throw new TypeError(`Expected a numeric value, but got ${value} instead.`)

if (Array.isArray(value) && value.length === 1)
value = value[0];

if (typeof value === 'object') {
converted = toPrimitive(value, 'number');

if (typeof converted === 'number')
return converted;

converted = toPrimitive(value, 'string');

if (typeof converted === 'string')
return converted;
}

throw new TypeError(`Expected a text or a numeric value, but got an ${typeof origValue} instead.`)
}

function bool(value) {
if (typeof value === 'boolean')
return value

if (typeof value === 'object' && value instanceof Boolean)
return value.valueOf();

throw new TypeError(`Expected a boolean (“true” or “false”) value, but got an ${typeof value} instead.`)
}

function arr(value) {
if (value === undefined || value === null) {
throw new TypeError(`Expected a list, but got ${value} instead.`)
}

coerceBoolean: function(value) {
if (typeof value === 'boolean')
return value
if (Array.isArray(value)) {
return value;
} else {
return [value];
}
}

// the parser is dynamically generated from generateParser.js at compile time

// Shared utility functions
const std =
{

if (typeof value === 'object' && value instanceof Boolean)
return value.valueOf();
isfn: function(fns, funcName) {
return hasOwnProperty(fns, funcName) && typeof fns[funcName] === "function";
},

throw new TypeError(`Expected a boolean (“true” or “false”) value, but got an ${typeof value} instead.`)
unknown: function(funcName) {
throw new ReferenceError('Unknown function: ' + funcName + '()');
},

coerceArray: arr,
coerceNumber: num,
coerceNumberOrString: numstr,
coerceBoolean: bool,

isSubset: function(a, b) {
const A = std.coerceArray(a);
const B = std.coerceArray(b);
const A = arr(a);
const B = arr(b);
return A.every( val => B.includes(val) );
},

Expand Down Expand Up @@ -1906,10 +1973,11 @@ var filtrex = (function (exports) {
if (arguments.length > 2) throw new TypeError('Too many arguments.');

options = typeof options === "object" ? options : {};
let {extraFunctions, customProp} = options;
for (let key of Object.getOwnPropertyNames(options))
let {extraFunctions, customProp, operators} = options;
for (const key of Object.keys(options))
{
if (key !== "extraFunctions" && key !== "customProp") throw new TypeError(`Unknown option: ${key}`);
if (!(["extraFunctions", "customProp", "operators"].includes(key)))
throw new TypeError(`Unknown option: ${key}`);
}


Expand All @@ -1931,13 +1999,39 @@ var filtrex = (function (exports) {
};

if (extraFunctions) {
for (var name in extraFunctions) {
if (hasOwnProperty(extraFunctions, name)) {
functions[name] = extraFunctions[name];
}
for (const name of Object.keys(extraFunctions)) {
functions[name] = extraFunctions[name];
}
}

let defaultOperators = {
'+': (a, b) => numstr(a) + numstr(b),
'-': (a, b) => b === undefined ? -num(a) : num(a) - num(b),
'*': (a, b) => num(a) * num(b),
'/': (a, b) => num(a) / num(b),

'%': (a, b) => mod(num(a), num(b)),
'^': (a, b) => Math.pow(num(a), num(b)),

'==': (a, b) => a === b,
'!=': (a, b) => a !== b,

'<': (a, b) => num(a) < num(b),
'<=': (a, b) => num(a) <= num(b),
'>=': (a, b) => num(a) >= num(b),
'>': (a, b) => num(a) > num(b),

'~=': (a, b) => RegExp(str(b)).test(str(a))
};

if (operators) {
for (const name of Object.keys(operators)) {
defaultOperators[name] = operators[name];
}
}

operators = defaultOperators;



// Compile the expression
Expand Down Expand Up @@ -1984,11 +2078,11 @@ var filtrex = (function (exports) {

// Patch together and return

let func = new Function('fns', 'std', 'prop', 'data', js.join(''));
let func = new Function('fns', 'ops', 'std', 'prop', 'data', js.join(''));

return function(data) {
try {
return func(functions, std, prop, data);
return func(functions, operators, std, prop, data);
}
catch (e)
{
Expand Down
2 changes: 1 addition & 1 deletion dist/browser/filtrex.min.js

Large diffs are not rendered by default.

Loading

0 comments on commit a2c0397

Please sign in to comment.