From abfa2a2e480b69d00d64b839b4e6f18174892a56 Mon Sep 17 00:00:00 2001 From: m93a Date: Tue, 21 Sep 2021 13:25:32 +0200 Subject: [PATCH] added constants #38 --- src/filtrex.d.ts | 10 ++++++++++ src/filtrex.mjs | 31 ++++++++++++++++++++----------- src/generateParser.mjs | 12 +++++++++--- test/misc.js | 28 ++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 14 deletions(-) diff --git a/src/filtrex.d.ts b/src/filtrex.d.ts index 0c3be49..88a9b76 100644 --- a/src/filtrex.d.ts +++ b/src/filtrex.d.ts @@ -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/src/filtrex.mjs b/src/filtrex.mjs index 5a0cc86..55f7afe 100644 --- a/src/filtrex.mjs +++ b/src/filtrex.mjs @@ -56,7 +56,7 @@ const std = } } - return JSON.stringify(built) + return built }, reduceRelation(arr) { @@ -100,12 +100,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 UnknownOptionError(key) - } + if (!knownOptions.includes(key)) throw new UnknownOptionError(key) @@ -160,6 +160,8 @@ export function compileExpression(expression, options) { operators = defaultOperators + constants = constants ?? {} + // Compile the expression @@ -171,8 +173,8 @@ 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 UnknownPropertyError(name) @@ -180,7 +182,7 @@ export function compileExpression(expression, options) { function safeGetter(obj) { return function get(name) { - if (hasOwnProperty(obj||{}, name)) + if (hasOwnProperty(obj ?? {}, name)) return obj[name] throw new UnknownPropertyError(name) @@ -188,11 +190,11 @@ export function compileExpression(expression, options) { } 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) @@ -200,6 +202,13 @@ export function compileExpression(expression, options) { } } + 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/src/generateParser.mjs b/src/generateParser.mjs index e6c06b5..0c0bccb 100644 --- a/src/generateParser.mjs +++ b/src/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/test/misc.js b/test/misc.js index 62f54e5..0ab4286 100644 --- a/test/misc.js +++ b/test/misc.js @@ -160,4 +160,32 @@ describe('Various other things', () => { expect( eval('4 + 3 not in (6, 8)') ).equals(true); }) + it('constants basics', () => { + const options = { constants: { pi: Math.PI, true: true, false: false }} + + expect( + compileExpression('2 * pi * radius', options)({ radius: 6 }) + ).equals(2 * Math.PI * 6) + + expect( + compileExpression('not true == false and not false == true', options)() + ).equals(true) + + expect( + compileExpression('pi', options)({ pi: 3 }) + ).equals(Math.PI) + + expect( + compileExpression(`'pi'`, options)({ pi: 3 }) + ).equals(3) + + + const options2 = { constants: { a: "a_const " } } + const data = { a: "a_data ", b: "b_data " } + const expr = `'a' + a + 'b' + b` + + expect( compileExpression(expr, options2)(data) ).equals("a_data a_const b_data b_data ") + + }) + });