Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ael

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ael - npm Package Compare versions

Comparing version 0.0.3 to 0.9.0

src/ael-error.js

3

Gruntfile.js

@@ -92,3 +92,4 @@ /*

"asty",
"cache-lru"
"cache-lru",
"source-code-error"
],

@@ -95,0 +96,0 @@ browserifyOptions: {

{
"name": "ael",
"version": "0.0.3",
"version": "0.9.0",
"description": "Advanced Expression Language",

@@ -25,3 +25,4 @@ "keywords": [ "expression", "language", "evaluation" ],

"asty": "1.8.11",
"cache-lru": "1.1.11"
"cache-lru": "1.1.11",
"source-code-error": "1.0.2"
},

@@ -28,0 +29,0 @@ "devDependencies": {

@@ -13,2 +13,13 @@

About
-----
Advanced Expression Language (AEL) is a JavaScript library for use
in the Browser and Node.js to parse/compile and execute/evaluate
JavaScript-style expressions. The expressions are based on conditional,
logical, bitwise, relational, arithmetical, functional, selective and
literal constructs and hence can express arbitrary complex matchings and
lookups. The result can be an arbitrary value, but usually is just a
boolean one.
Installation

@@ -21,93 +32,164 @@ ------------

About
Usage
-----
Advanced Expression Language (AEL) is a library in JavaScript for use
in the Browser and Node.js to parse and evaluate JavaScript-style
expressions.
```
$ cat sample.js
const AEL = require("..")
Expression Language
-------------------
const ael = new AEL({ trace: (msg) => console.log(msg) })
### By Example
const expr = `foo.quux =~ /ux$/ && foo.bar.a == 1`
FIXME
const data = {
foo: {
bar: { a: 1, b: 2, c: 3 },
baz: [ "a", "b", "c", "d", "e" ],
quux: "quux"
}
}
### By Grammar
try {
const result = ael.evaluate(expr, data)
console.log("RESULT", result)
}
catch (ex) {
console.log("ERROR", ex.toString())
}
FIXME
$ node sample.js
AEL: compile: +---(expression string)---------------------------------------------------------------------------------
AEL: compile: | foo.quux =~ /ux$/ && foo.bar.a == 1
AEL: compile: +---(abstract syntax tree)------------------------------------------------------------------------------
AEL: compile: | Logical (op: "&&", expr: "foo.quux =~ /ux$/ && foo.bar.a == 1") [1,1]
AEL: compile: | ├── Relational (op: "=~") [1,1]
AEL: compile: | │ ├── Select [1,1]
AEL: compile: | │ │ ├── Variable (id: "foo") [1,1]
AEL: compile: | │ │ └── SelectItem [1,4]
AEL: compile: | │ │ └── Identifier (id: "quux") [1,5]
AEL: compile: | │ └── LiteralRegExp (value: /ux$/) [1,13]
AEL: compile: | └── Relational (op: "==") [1,22]
AEL: compile: | ├── Select [1,22]
AEL: compile: | │ ├── Variable (id: "foo") [1,22]
AEL: compile: | │ ├── SelectItem [1,25]
AEL: compile: | │ │ └── Identifier (id: "bar") [1,26]
AEL: compile: | │ └── SelectItem [1,29]
AEL: compile: | │ └── Identifier (id: "a") [1,30]
AEL: compile: | └── LiteralNumber (value: 1) [1,35]
AEL: execute: +---(evaluation recursion tree)-------------------------------------------------------------------------
AEL: execute: | Logical {
AEL: execute: | Relational {
AEL: execute: | Select {
AEL: execute: | Variable {
AEL: execute: | }: {"bar":{"a":1,"b":2,"c":3},"baz":["a","b...
AEL: execute: | Identifier {
AEL: execute: | }: "quux"
AEL: execute: | }: "quux"
AEL: execute: | LiteralRegExp {
AEL: execute: | }: {}
AEL: execute: | }: true
AEL: execute: | Relational {
AEL: execute: | Select {
AEL: execute: | Variable {
AEL: execute: | }: {"bar":{"a":1,"b":2,"c":3},"baz":["a","b...
AEL: execute: | Identifier {
AEL: execute: | }: "bar"
AEL: execute: | Identifier {
AEL: execute: | }: "a"
AEL: execute: | }: 1
AEL: execute: | LiteralNumber {
AEL: execute: | }: 1
AEL: execute: | }: true
AEL: execute: | }: true
```
expr ::= conditional
| logical
| bitwise
| relational
| arithmentical
| function-call
| attribute-ref
| query-parameter
| literal
| parenthesis
| sub-query
conditional ::= expr "?" expr ":" expr
| expr "?:" expr
logical ::= expr ("&&" | "||") expr
| "!" expr
bitwise ::= expr ("&" | "|" | "<<" | ">>") expr
| "~" expr
relational ::= expr ("==" | "!=" | "<=" | ">=" | "<" | ">" | "=~" | "!~") expr
arithmethical ::= expr ("+" | "-" | "*" | "/" | "%" | "**") expr
function-call ::= id "(" (expr ("," expr)*)? ")"
attribute-ref ::= "@" (id | string)
query-parameter ::= "{" id "}"
id ::= /[a-zA-Z_][a-zA-Z0-9_-]*/
literal ::= string | regexp | number | value
string ::= /"(\\"|.)*"/ | /'(\\'|.)*'/
regexp ::= /`(\\`|.)*`/
number ::= /\d+(\.\d+)?$/
value ::= "true" | "false" | "null" | "NaN" | "undefined"
parenthesis ::= "(" expr ")"
sub-query ::= path // <-- ESSENTIAL RECURSION !!
Expression Language
-------------------
Application Programming Interface (API)
---------------------------------------
The following BNF-style grammar shows the supported expression language:
### AEL API
```
// top-level
expr ::= conditional
| logical
| bitwise
| relational
| arithmentical
| functional
| selective
| variable
| literal
| parenthesis
- `new AEL(): AEL`:<br/>
Create a new AEL instance.
// expressions
conditional ::= expr "?" expr ":" expr
| expr "?:" expr
logical ::= expr ("&&" | "||") expr
| "!" expr
bitwise ::= expr ("&" | "^" | "|" | "<<" | ">>") expr
| "~" expr
relational ::= expr ("==" | "!=" | "<=" | ">=" | "<" | ">" | "=~" | "!~") expr
arithmethical ::= expr ("+" | "-" | "*" | "/" | "%" | "**") expr
functional ::= expr "?."? "(" (expr ("," expr)*)? ")"
selective ::= expr "?."? "." ud
| expr "?."? "[" expr "]"
variable ::= id
literal ::= array | object | string | regexp | number | value
parenthesis ::= "(" expr ")"
- `AEL#cache(num: Number): AEL`:<br/>
Set the upper limit for the internal query cache to `num`, i.e.,
up to `num` ASTs of parsed queries will be cached. Set `num` to
`0` to disable the cache at all. Returns the API itself.
// literals
id ::= /[a-zA-Z_][a-zA-Z0-9_-]*/
array ::= "[" (expr ("," expr)*)? "]"
object ::= "{" (key ":" expr ("," key ":" expr)*)? "}"
key ::= "[" expr "]"
| id
string ::= /"(\\"|.)*"/
| /'(\\'|.)*'/
regexp ::= /`(\\`|.)*`/
number ::= /[+-]?/ number-value
number-value ::= "0b" /[01]+/
| "0o" /[0-7]+/
| "0x" /[0-9a-fA-F]+/
| /[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?/
| /[0-9]+/
value ::= "true" | "false" | "null" | "NaN" | "undefined"
```
- `AEL#compile(selector: String, trace?: Boolean): AELQuery {
Compile `selector` DSL into an internal query object for subsequent
processing by `AEL#execute`.
If `trace` is `true` the compiling is dumped to the console.
Returns the query object.
Application Programming Interface (API)
---------------------------------------
- `AEL#execute(node: Object, query: AELQuery, params?: Object, trace?: Boolean): Object[]`:<br/>
Execute the previously compiled `query` (see `compile` above) at `node`.
The optional `params` object can provide parameters for the `{name}` query constructs.
If `trace` is `true` the execution is dumped to the console.
Returns an array of zero or more matching AST nodes.
The following TypeScript definition shows the supported Application Programming Interface (API):
- `AEL#evaluate(node: Object, selector: String, params?: Object, trace?: Boolean): Object[]`: <br/>
Just the convenient combination of `compile` and `execute`:
`execute(node, compile(selector, trace), params, trace)`.
Use this as the standard query method except you need more control.
The optional `params` object can provide parameters for the `{name}` query constructs.
If `trace` is `true` the compiling and execution is dumped to the console.
Returns an array of zero or more matching AST nodes.
```ts
declare module "AEL" {
class AEL {
/* create AEL instance */
public constructor(
options?: {
cache?: number, /* number of LRU-cached ASTs (default: 0) */
trace?: ( /* optional tracing callback (default: null) */
msg: string /* tracing message */
) => void
}
)
Example
-------
/* individual step 1: compile (and cache) expression into AST */
compile(
expr: string /* expression string */
): any /* abstract syntax tree */
```
$ cat sample.js
FIXME
/* individual step 2: execute AST */
execute(
ast: any, /* abstract syntax tree */
vars: object /* expression variables */
): void
$ node sample.js
FIXME
/* all-in-one step: evaluate (compile and execute) expression */
evaluate(
expr: string, /* expression string */
vars: object /* expression variables */
): any
}
export = AEL
}
```

@@ -114,0 +196,0 @@

const AEL = require("..")
let ael = new AEL()
let ast = ael.compile(`foo.bar.quux()`, true)
let result = ael.execute(ast, { foo: { bar: { quux: () => 42 } } }, true)
console.log(result)
const ael = new AEL({ trace: (msg) => console.log(msg) })
const expr = `foo.quux =~ /ux$/ && foo.bar.a == 1`
const data = {
foo: {
bar: { a: 1, b: 2, c: 3 },
baz: [ "a", "b", "c", "d", "e" ],
quux: "quux"
}
}
try {
const result = ael.evaluate(expr, data)
console.log("RESULT", result)
}
catch (ex) {
console.log("ERROR", ex.toString())
}

@@ -25,12 +25,27 @@ /*

import util from "./ael-util.js"
import AELTrace from "./ael-trace.js"
/* load internal depdendencies */
import util from "./ael-util.js"
import AELTrace from "./ael-trace.js"
import AELError from "./ael-error.js"
/* the exported class */
export default class AELEval extends AELTrace {
constructor (vars, trace) {
super()
this.vars = vars
this.trace = trace
constructor (expr, vars, trace) {
super(trace)
this.expr = expr
this.vars = vars
}
/* raise an error */
error (N, origin, message) {
let pos = N.pos()
return new AELError(message, {
origin: origin,
code: this.expr,
line: pos.line,
column: pos.column
})
}
/* evaluate an arbitrary node */
eval (N) {

@@ -49,2 +64,5 @@ switch (N.type()) {

case "Variable": return this.evalVariable(N)
case "LiteralArray": return this.evalLiteralArray(N)
case "LiteralObject": return this.evalLiteralObject(N)
case "LiteralObjectItem": return this.evalLiteralObjectItem(N)
case "LiteralString": return this.evalLiteralString(N)

@@ -55,6 +73,7 @@ case "LiteralRegExp": return this.evalLiteralRegExp(N)

default:
throw new Error("invalid AST node")
throw this.error(N, "eval", "invalid AST node (should not happen)")
}
}
/* evaluate conditional binary operator */
evalConditionalBinary (N) {

@@ -69,2 +88,3 @@ this.traceBegin(N)

/* evaluate conditional ternary operator */
evalConditionalTernary (N) {

@@ -81,2 +101,3 @@ this.traceBegin(N)

/* evaluate logical operator */
evalLogical (N) {

@@ -101,2 +122,3 @@ this.traceBegin(N)

/* evaluate bitwise operator */
evalBitwise (N) {

@@ -117,2 +139,3 @@ this.traceBegin(N)

/* evaluate relational operator */
evalRelational (N) {

@@ -124,10 +147,122 @@ this.traceBegin(N)

switch (N.get("op")) {
case "==": result = v1 === v2; break
case "!=": result = v1 !== v2; break
case "<=": result = util.coerce(v1, "number") <= util.coerce(v2, "number"); break
case ">=": result = util.coerce(v1, "number") >= util.coerce(v2, "number"); break
case "<": result = util.coerce(v1, "number") < util.coerce(v2, "number"); break
case ">": result = util.coerce(v1, "number") > util.coerce(v2, "number"); break
case "=~": result = util.coerce(v1, "string").match(util.coerce(v2, "regexp")) !== null; break
case "!~": result = util.coerce(v1, "string").match(util.coerce(v2, "regexp")) === null; break
case "==":
switch (util.typePair(v1, v2)) {
case "array:array":
case "array:object":
case "array:scalar":
case "object:array":
case "object:object":
case "object:scalar":
v1 = util.coerce(v1, "array")
v2 = util.coerce(v2, "array")
result = v1.length === v2.length
&& v1.filter((x) => !v2.includes(x)).length === 0
&& v2.filter((x) => !v1.includes(x)).length === 0
break
default:
result = v1 === v2
}
break
case "!=":
switch (util.typePair(v1, v2)) {
case "array:array":
case "array:object":
case "array:scalar":
case "object:array":
case "object:object":
case "object:scalar":
v1 = util.coerce(v1, "array")
v2 = util.coerce(v2, "array")
result = v1.length !== v2.length
|| v1.filter((x) => !v2.includes(x)).length > 0
|| v2.filter((x) => !v1.includes(x)).length > 0
break
default:
result = v1 !== v2
}
break
case "<=":
switch (util.typePair(v1, v2)) {
case "string:any":
result = v1.localeCompare(util.coerce(v2, "string")) <= 0
break
case "array:array":
case "array:object":
case "array:scalar":
case "object:array":
case "object:object":
case "object:scalar":
v1 = util.coerce(v1, "array")
v2 = util.coerce(v2, "array")
result = v1.filter((x) => !v2.includes(x)).length === 0
break
default:
result = util.coerce(v1, "number") <= util.coerce(v2, "number")
}
break
case "<":
switch (util.typePair(v1, v2)) {
case "string:any":
result = v1.localeCompare(util.coerce(v2, "string")) < 0
break
case "array:array":
case "array:object":
case "array:scalar":
case "object:array":
case "object:object":
case "object:scalar":
v1 = util.coerce(v1, "array")
v2 = util.coerce(v2, "array")
result = v1.filter((x) => !v2.includes(x)).length === 0
&& v2.filter((x) => !v1.includes(x)).length > 0
break
default:
result = util.coerce(v1, "number") < util.coerce(v2, "number")
}
break
case ">=":
switch (util.typePair(v1, v2)) {
case "string:any":
result = v1.localeCompare(util.coerce(v2, "string")) >= 0
break
case "array:array":
case "array:object":
case "array:scalar":
case "object:array":
case "object:object":
case "object:scalar":
v1 = util.coerce(v1, "array")
v2 = util.coerce(v2, "array")
result = v2.filter((x) => !v1.includes(x)).length === 0
break
default:
result = util.coerce(v1, "number") >= util.coerce(v2, "number")
}
break
case ">":
switch (util.typePair(v1, v2)) {
case "string:any":
result = v1.localeCompare(util.coerce(v2, "string")) > 0
break
case "array:array":
case "array:object":
case "array:scalar":
case "object:array":
case "object:object":
case "object:scalar":
v1 = util.coerce(v1, "array")
v2 = util.coerce(v2, "array")
result = v2.filter((x) => !v1.includes(x)).length === 0
&& v1.filter((x) => !v2.includes(x)).length > 0
break
default:
result = util.coerce(v1, "number") > util.coerce(v2, "number")
}
break
case "=~":
result = util.coerce(v1, "string").match(util.coerce(v2, "regexp")) !== null
break
case "!~":
result = util.coerce(v1, "string").match(util.coerce(v2, "regexp")) === null
break
}

@@ -138,2 +273,3 @@ this.traceEnd(N, result)

/* evaluate arithmetical operator */
evalArithmetical (N) {

@@ -146,10 +282,99 @@ this.traceBegin(N)

case "+":
if (typeof v1 === "string")
result = v1 + util.coerce(v2, "string")
else
result = util.coerce(v1, "number") + util.coerce(v2, "number")
switch (util.typePair(v1, v2)) {
case "string:any":
result = v1 + util.coerce(v2, "string")
break
case "array:array":
result = v1.concat(v2)
break
case "array:object":
result = v1.concat(Object.keys(v2).filter((x) => util.truthy(x)))
break
case "array:scalar":
result = v1.concat([ v2 ])
break
case "object:array":
result = { ...v1, ...v2.reduce((obj, key) => { obj[key] = true; return obj }, {}) }
break
case "object:object":
result = { ...v1, ...v2 }
break
case "object:scalar":
result = { ...v1, [util.coerce(v2, "string")]: true }
break
default:
result = util.coerce(v1, "number") + util.coerce(v2, "number")
}
break
case "-": result = util.coerce(v1, "number") + util.coerce(v2, "number"); break
case "-":
switch (util.typePair(v1, v2)) {
case "string:any": {
let i = v1.indexOf(v2)
result = i >= 0 ? v1.splice(i, v2.length) : v1
break
}
case "array:array":
result = v1.filter((x) => !v2.includes(x))
break
case "array:object":
result = v1.filter((x) => !util.truthy(v2[x]))
break
case "array:scalar":
result = v1.filter((x) => x !== v2)
break
case "object:array":
result = Object.keys(v1)
.filter((key) => !v2.includes(key))
.reduce((obj, key) => { obj[key] = v1[key]; return obj }, {})
break
case "object:object":
result = Object.keys(v1)
.filter((key) => !util.truthy(v2[key]))
.reduce((obj, key) => { obj[key] = v1[key]; return obj }, {})
break
case "object:scalar":
result = Object.keys(v1)
.filter((key) => key !== v2)
.reduce((obj, key) => { obj[key] = v1[key]; return obj }, {})
break
default:
result = util.coerce(v1, "number") - util.coerce(v2, "number")
}
break
case "/":
switch (util.typePair(v1, v2)) {
case "string:any": {
let i = v1.indexOf(v2)
result = i >= 0 ? v2 : ""
break
}
case "array:array":
result = v1.filter((x) => v2.includes(x))
break
case "array:object":
result = v1.filter((x) => util.truthy(v2[x]))
break
case "array:scalar":
result = v1.filter((x) => x === v2)
break
case "object:array":
result = Object.keys(v1)
.filter((key) => v2.includes(key))
.reduce((obj, key) => { obj[key] = v1[key]; return obj }, {})
break
case "object:object":
result = Object.keys(v1)
.filter((key) => util.truthy(v2[key]))
.reduce((obj, key) => { obj[key] = v1[key]; return obj }, {})
break
case "object:scalar":
result = Object.keys(v1)
.filter((key) => key === v2)
.reduce((obj, key) => { obj[key] = v1[key]; return obj }, {})
break
default:
result = util.coerce(v1, "number") / util.coerce(v2, "number")
}
break
case "*": result = util.coerce(v1, "number") * util.coerce(v2, "number"); break
case "/": result = util.coerce(v1, "number") / util.coerce(v2, "number"); break
case "%": result = util.coerce(v1, "number") % util.coerce(v2, "number"); break

@@ -162,2 +387,3 @@ case "**": result = Math.pow(util.coerce(v1, "number"), util.coerce(v2, "number")); break

/* evaluate unary operator */
evalUnary (N) {

@@ -175,45 +401,47 @@ this.traceBegin(N)

evalSelect (N) {
/* evaluate selection */
evalSelect (N, provideParent = false) {
this.traceBegin(N)
let parent
let result = this.eval(N.child(0))
for (const child of N.childs(1)) {
if (typeof result !== "object")
throw new Error("selector base object does not evaluate into an object")
const selector = this.eval(child)
const optional = child.get("optional") ?? false
if ((result === null || result === undefined) && optional) {
result = undefined
break
}
if (result === null || typeof result !== "object")
throw this.error(child, "evalSelect", "selector base object does not evaluate into a non-null object")
const selector = this.eval(child.child(0))
const key = util.coerce(selector, "string")
parent = result
result = result[key]
}
this.traceEnd(N, result)
return result
return provideParent ? [ parent, result ] : result
}
/* evaluate function call */
evalFuncCall (N) {
this.traceBegin(N)
let S = N.child(0)
this.traceBegin(S)
let ctx = null
let ctx
let fn = null
if (S.type() === "Variable")
if (S.type() === "Select")
[ ctx, fn ] = this.evalSelect(S, true)
else
fn = this.eval(S)
else if (S.type() === "Select") {
fn = this.eval(S.child(0))
for (const child of S.childs(1)) {
if (typeof fn !== "object")
throw new Error("selector base object does not evaluate into an object")
const selector = this.eval(child)
const key = util.coerce(selector, "string")
ctx = fn
fn = fn[key]
}
const optional = N.get("optional") ?? false
let result
if ((fn === null || fn === undefined) && optional)
result = undefined
else {
if (typeof fn !== "function")
throw this.error(S, "evalFuncCall", "object does not evaluate into a function")
let args = []
N.childs().forEach((child) => {
args.push(this.eval(child))
})
result = fn.apply(ctx, args)
}
this.traceEnd(S, fn)
if (typeof fn !== "function")
throw new Error("selector tail object does not evaluate into a function")
let args = []
N.childs().forEach((child) => {
args.push(this.eval(child))
})
let result = fn.apply(ctx, args)
this.traceEnd(N, result)

@@ -223,2 +451,3 @@ return result

/* evaluate identifier */
evalIdentifier (N) {

@@ -231,2 +460,3 @@ this.traceBegin(N)

/* evaluate variable */
evalVariable (N) {

@@ -236,3 +466,3 @@ this.traceBegin(N)

if (typeof this.vars[id] === "undefined")
throw new Error("invalid variable reference \"" + id + "\"")
throw this.error(N, "evalVariable", "invalid variable reference")
let result = this.vars[id]

@@ -243,2 +473,35 @@ this.traceEnd(N, result)

/* evaluate array literal */
evalLiteralArray (N) {
this.traceBegin(N)
let result = []
for (const child of N.childs())
result.push(this.eval(child))
this.traceEnd(N, result)
return result
}
/* evaluate object literal */
evalLiteralObject (N) {
this.traceBegin(N)
let result = {}
for (const child of N.childs()) {
const sub = this.eval(child)
result = { ...result, ...sub }
}
this.traceEnd(N, result)
return result
}
/* evaluate object item */
evalLiteralObjectItem (N) {
this.traceBegin(N)
const key = this.eval(N.child(0))
const val = this.eval(N.child(1))
const result = { [key]: val }
this.traceEnd(N, result)
return result
}
/* evaluate string literal */
evalLiteralString (N) {

@@ -251,2 +514,3 @@ this.traceBegin(N)

/* evaluate regular expression literal */
evalLiteralRegExp (N) {

@@ -259,2 +523,3 @@ this.traceBegin(N)

/* evaluate number literal */
evalLiteralNumber (N) {

@@ -267,2 +532,3 @@ this.traceBegin(N)

/* evaluate special value literal */
evalLiteralValue (N) {

@@ -269,0 +535,0 @@ this.traceBegin(N)

@@ -25,7 +25,11 @@ /*

/* eslint no-console: 0 */
/* load internal depdendencies */
import util from "./ael-util.js"
/* the exported class */
export default class AELTrace {
constructor (trace = null) {
this.trace = trace
}
/* determine output prefix based on tree depth */

@@ -42,6 +46,6 @@ prefixOf (N) {

traceBegin (N) {
if (!this.trace)
if (this.trace === null)
return
let prefix = this.prefixOf(N)
console.log("AEL: execute: | " + prefix + N.type() + " {")
this.trace("AEL: execute: | " + prefix + N.type() + " {")
}

@@ -51,3 +55,3 @@

traceEnd (N, val) {
if (!this.trace)
if (this.trace === null)
return

@@ -64,5 +68,5 @@ let prefix = this.prefixOf(N)

result = result.substr(0, 40) + "..."
console.log("AEL: execute: | " + prefix + "}: " + result)
this.trace("AEL: execute: | " + prefix + "}: " + result)
}
}

@@ -57,2 +57,4 @@ /*

result = value.length > 0
else
result = Object.keys(value).length > 0
}

@@ -88,2 +90,14 @@ break

break
case "array":
if (typeof value === "object" && !(value instanceof Array) && value !== null)
value = Object.keys(value)
else if (!(typeof value === "object" && value instanceof Array))
value = [ String(value) ]
break
case "object":
if (typeof value === "object" && value instanceof Array)
value = value.reduce((obj, el) => { obj[el] = true; return obj }, {})
else if (!(typeof value === "object"))
value = { [String(value)]: true }
break
}

@@ -97,2 +111,29 @@ }

}
/* determine type pair */
static typePair (v1, v2) {
if (typeof v1 === "string")
return "string:any"
else if (typeof v1 === "object" && v1 !== null) {
if (v1 instanceof Array) {
if (typeof v2 === "object" && v2 instanceof Array && v2 !== null)
return "array:array"
else if (typeof v2 === "object" && !(v2 instanceof Array) && v2 !== null)
return "array:object"
else
return "array:scalar"
}
else {
if (typeof v2 === "object" && v2 instanceof Array && v2 !== null)
return "object:array"
else if (typeof v2 === "object" && !(v2 instanceof Array) && v2 !== null)
return "object:object"
else
return "object:scalar"
}
}
else
return "any:any"
}
}

@@ -32,2 +32,3 @@ /*

import AELEval from "./ael-eval.js"
import AELError from "./ael-error.js"

@@ -45,17 +46,18 @@ /* get expression parser (by loading and on-the-fly compiling PEG.js grammar) */

/* create a new AEL instance */
constructor () {
constructor (options = {}) {
/* provide parameter defaults */
this.options = {
cache: 100,
trace: null,
...options
}
/* create LRU cache */
this._cache = new CacheLRU()
this._cache.limit(this.options.cache)
}
/* configure the LRU cache limit */
cache (entries) {
if (arguments.length !== 1)
throw new Error("AEL#cache: invalid number of arguments")
this._cache.limit(entries)
return this
}
/* individual step 1: compile expression into AST */
compile (expr, trace) {
compile (expr) {
/* sanity check usage */
if (arguments.length < 1)

@@ -65,10 +67,13 @@ throw new Error("AEL#compile: too less arguments")

throw new Error("AEL#compile: too many arguments")
if (trace === undefined)
trace = false
/* tracing operation */
if (this.options.trace !== null)
this.options.trace("AEL: compile: +---(expression string)-----------------" +
"----------------------------------------------------------------\n" +
expr.replace(/\n$/, "").replace(/^/mg, "AEL: compile: | "))
/* try to fetch pre-compiled AST */
let ast = this._cache.get(expr)
if (ast === undefined) {
if (trace)
console.log("AEL: compile: +---(expression)------------------------" +
"----------------------------------------------------------------\n" +
expr.replace(/\n$/, "").replace(/^/mg, "AEL: compile: | "))
/* compile AST from scratch */
const asty = new ASTY()

@@ -81,12 +86,26 @@ let result = PEGUtil.parse(AELParser, expr, {

})
if (result.error !== null)
throw new Error("AEL: compile: expression parsing failed:\n" +
PEGUtil.errorMessage(result.error, true).replace(/^/mg, "ERROR: "))
if (result.error !== null) {
const message = "parsing failed: " +
`"${result.error.location.prolog}" "${result.error.location.token}" "${result.error.location.epilog}": ` +
result.error.message
throw new AELError(message, {
origin: "parser",
code: expr,
line: result.error.line,
column: result.error.column
})
}
ast = result.ast
if (trace)
console.log("AEL: compile: +---(AST)-------------------------------" +
"----------------------------------------------------------------\n" +
ast.dump().replace(/\n$/, "").replace(/^/mg, "AEL: compile: | "))
ast.set("expr", expr)
/* cache AST for subsequent usages */
this._cache.set(expr, ast)
}
/* tracing operation */
if (this.options.trace !== null)
this.options.trace("AEL: compile: +---(abstract syntax tree)--------------" +
"----------------------------------------------------------------\n" +
ast.dump().replace(/\n$/, "").replace(/^/mg, "AEL: compile: | "))
return ast

@@ -96,3 +115,4 @@ }

/* individual step 2: execute AST */
execute (ast, vars, trace) {
execute (ast, vars) {
/* sanity check usage */
if (arguments.length < 1)

@@ -102,16 +122,21 @@ throw new Error("AEL#execute: too less arguments")

throw new Error("AEL#execute: too many arguments")
/* provide defaults */
if (vars === undefined)
vars = {}
if (trace === undefined)
trace = false
if (trace)
console.log("AEL: execute: +---(result)----------------------------" +
/* tracing operation */
if (this.options.trace !== null)
this.options.trace("AEL: execute: +---(evaluation recursion tree)---------" +
"----------------------------------------------------------------")
const evaluator = new AELEval(vars, trace)
const result = evaluator.eval(ast)
return result
/* evaluate the AST */
const expr = ast.get("expr")
const evaluator = new AELEval(expr, vars, this.options.trace)
return evaluator.eval(ast)
}
/* all-in-one step */
evaluate (expr, vars, trace) {
evaluate (expr, vars) {
/* sanity check usage */
if (arguments.length < 1)

@@ -121,8 +146,10 @@ throw new Error("AEL#evaluate: too less arguments")

throw new Error("AEL#evaluate: too many arguments")
/* provide defaults */
if (vars === undefined)
vars = {}
if (trace === undefined)
trace = false
const ast = this.compile(expr, trace)
return this.execute(ast, vars, trace)
/* compile and evaluate expression */
const ast = this.compile(expr)
return this.execute(ast, vars)
}

@@ -129,0 +156,0 @@ }

@@ -38,5 +38,3 @@ /*

const ael = new AEL()
it("API availability", () => {
expect(ael).to.respondTo("cache")
expect(ael).to.respondTo("compile")

@@ -46,4 +44,3 @@ expect(ael).to.respondTo("execute")

})
it("simple expressions", () => {
it("literal expressions", () => {
expect(ael.evaluate("true")).to.be.equal(true)

@@ -53,9 +50,10 @@ expect(ael.evaluate("42")).to.be.equal(42)

})
it("variable expressions", () => {
expect(ael.evaluate("foo + bar", { foo: "foo", bar: "bar" })).to.be.equal("foobar")
it("conditional expressions", () => {
expect(ael.evaluate("a > 0 ? b : c", { a: 1, b: 2, c: 3 })).to.be.equal(2)
expect(ael.evaluate("a > 0 ? b > 1 ? true : false : c", { a: 1, b: 2, c: 3 })).to.be.equal(true)
})
it("function calls", () => {
expect(ael.evaluate("foo() + bar.baz()", { foo: () => 42, bar: { baz: () => 7 } }), true).to.be.equal(49)
it("boolean expressions", () => {
expect(ael.evaluate("a && b || c && d", { a: true, b: true, c: false, d: false })).to.be.equal(true)
})
})

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc