библиотека исполняющая безопасные динамические выражения JavaScript
// загрузка в браузер:
// <script src="lib/polyfill.min.js"></script>
// <script src="lib/static-eval.js"></script>
// загрузка в исходнике нода integra-node-red:
// var { safeEval } = this.context().global.get('static-eval')
// загрузка в function ноде integra-node-red:
// var { safeEval } = global.get('static-eval')
// загрузка в nodejs:
require("babel-polyfill")
Object.assign(global, require('./static-eval.js'))
'ok'
static-eval-api static-eval v1.16
'ok'
parse evaluate safeEval wildcard apply Оператор in как в python Упрощенное сравнение строки с регулярным выражением Упрощенное сравнение строк с использованием подстановочного знака Упрощенный доступ к значению параметров FireFly Упрощенный доступ к параметрам и пинам FireFly Оператор new Оператор instanceof Ключевое слово this Специальная функция apply Еще примеры
компиляция выражения в абстрактное синтаксическое дерево
var expr = '[1,2,3+4*10+n,foo(3+5),obj[""+"x"].y]'
var ast = parse(expr)
ast
ArrayExpression {
type: 'ArrayExpression',
elements: [
Literal { type: 'Literal', value: 1, raw: '1' },
Literal { type: 'Literal', value: 2, raw: '2' },
BinaryExpression {
type: 'BinaryExpression',
operator: '+',
left: [BinaryExpression],
right: [Identifier]
},
CallExpression {
type: 'CallExpression',
callee: [Identifier],
arguments: [Array]
},
StaticMemberExpression {
type: 'MemberExpression',
computed: false,
object: [ComputedMemberExpression],
property: [Identifier]
}
]
}
исполнение абстрактного синтаксического дерева в заданном контексте
var vars = {
n: 6,
foo: function (x) { return x * 100 },
obj: { x: { y: 555 } }
}
evaluate(ast, vars)
[ 1, 2, 49, 800, 555 ]
безопасный вызов parse и затем evaluate с указаным контекстом и опциональной функцией обработки ошибок
[ safeEval('x+1', {x:123}), safeEval('x.value', {x:123}, onerror=console.log) ]
ERROR(safeEval): x.value; Cannot use 'in' operator to search for 'value' in 123 Cannot use 'in' operator to search for 'value' in 123
[ 124, undefined ]
сравнение строк с использованием звездочки в качестве подстановочного знака
[wildcard('alarm*')('alarm.openCase'),
wildcard('*openCase')('alarm.openCase'),
wildcard('alarm*')('ok.normal'),
wildcard('*openCase')('ok.normal'),]
[ true, true, false, false ]
функция табличной выборки принимает первым аргументом таблицу, т.е. javascript объект, затем типизированный аргумент выбоки и опциональное значение результата по умолчанию
console.log(apply({ "alarm.unlikely": 10, "alarm.likely": 60 }, 55))
console.log(apply({ "StreetLighting": { "*": 0 } }, ['StreetLighting','ok.normal']))
console.log(apply({ "BOLID_ContactSensor": { "alarm.*": 2 } }, {
types: ['Item', 'BOLID_ContactSensor'],
get(name) { return 'state' == name && { value: 'alarm.longOpen' } }
}))
alarm.unlikely 0 2
Возможно использование оператора in с поведением не как в javascript, а как в python, т.е. если правый операнд является массивом или строкой, то обычная функция оператора (т.е. проверка наличия ключа) заменяется на вызов соответствующей функции includes.
function test(expr, context={}) {
console.log('expr:', expr);
var ast = parse(expr);
// console.log('ast:', JSON.stringify(ast,null,2));
var res = evaluate(ast, {});
console.log('res:', res);
console.log();
}
test("'x' in ({ x: 1 })");
test("'x' in ({ y: 1 })");
test("'x' in '1'");
test("'x' in '1x2'");
test("'x' in []")
test("'x' in [1,2,3]")
test("'x' in [1,2,3,'x']")
expr: 'x' in ({ x: 1 })
res: true
expr: 'x' in ({ y: 1 })
res: false
expr: 'x' in '1'
res: false
expr: 'x' in '1x2'
res: true
expr: 'x' in []
res: false
expr: 'x' in [1,2,3]
res: false
expr: 'x' in [1,2,3,'x']
res: true
Если одним из операндов операторов == и != является регулярное выражение, а другим x, то выражение x == /^alarm/ заменяется на !!x.match(/^alarm/)
[safeEval("'alarm.alarm' == /^alarm/"),
safeEval("'alarm.alarm' != /^alarm/"),
safeEval("'ok.normal' == /^alarm/"),
safeEval("'ok.normal' != /^alarm/")]
[ true, false, false, true ]
Если одним из операндов операторов == и != является строковая литера с использованием звездочки, а другим x, то выражение x == 'alarm' заменяется на wildcard('alarm')(x)
[safeEval("'alarm.alarm' == 'alarm*'"),
safeEval("'alarm.alarm' != 'alarm*'"),
safeEval("'ok.normal' == 'alarm*'"),
safeEval("'ok.normal' != 'alarm*'")]
[ true, false, false, true ]
Если аргумент операции x является javascript объектом и имеет атрибут value, то доступ к этому значению можно осуществлять, как обычным образом x.value так и сокращенным x
var ctx = { x: { value: 123 } }
console.log(safeEval('x.value', ctx))
console.log(safeEval('x', ctx))
123 123
Если аргумент x операции индексации по ключу key является javascript объектом и имеет атрибуты params и\или pins, а так же key в нем не обнаружен, тогда библиотека пытается найти key сначала в params, затем в pins. т.е. доступ к параметру или пину можно осуществлять, как обычным образом x.pins['pin_name'].params['param_name'].value так и сокращенным x.pin_name.param_name
var ctx = {
x: {
pins: {
PinName: {
params: {
paramName: { value: 123 }
}
}
}
}
}
console.log(safeEval("x.pins.PinName.params.paramName.value", ctx))
console.log(safeEval('x.PinName.paramName', ctx))
123 123
Оператор new можно использовать если в контексте есть соответствующий ключ, который является функцией которая принимает имя конструктора в качестве идентификатора и возвращает другую функцию принимающую уже аргументы конструктора
safeEval("new Event(123)", {new: (name) => (arg) => name+arg})
'Event123'
Оператор instanceof можно использовать если в контексте есть соответствующий ключ, который является функцией которая принимает в качестве аргументов объекта слева и имя конструктора справа
[safeEval("x instanceof X", {x: 1,instanceof: (obj,type) => type === 'X'}),
safeEval("x instanceof Y", {x: 1,instanceof: (obj,type) => type === 'X'})]
[ true, false ]
Ключевое слово this возвращает ссылку на контекст переданный вторым параметром в функцию evaluate
[safeEval("x+1", {x: 1}), safeEval("this.x+1", {x: 1})]
[ 2, 2 ]
Если в контексте задана специальная функция apply то любое значение может быть вызвано как функция в коде выражений
safeEval("123(true,'',1)", {apply: (table, ...args) => console.log(table,args)})
123 [ true, '', 1 ]
// возьмем небольшую и наглядную вырезку из реального объекта FireFly
var x = {
serverId: '8bc32339-08ee-4198-954f-fafbf4f2fd5b',
id: '023d7b3f-8293-4852-8c7d-60962efbacf8',
types: [ 'Item', 'Computer' ],
params: {
name: {
value: 'имя компьютера',
datetime: 1593228361,
status: null,
version: 25215174
},
voltage_5VSB_max: {
value: 5.5,
datetime: 1593228361,
status: null,
version: 25225184
},
state: {
value: 'ok.stateUpdate',
datetime: 1593228361,
status: null,
version: 25225196
},
},
pins: {
imageInput: { type: 'ImageInput', params: {} },
rs232Socket1: {
type: 'Rs232Socket',
params: {
port: {
value: 'COM1',
datetime: 1593228361,
status: null,
version: 25227518 }
}
},
},
linkedItems: [ '023d7b3f-8293-4852-8c7d-60962efbacf8', 'd40d398a-8724-4143-84b3-cdb95ee9d5b3' ]
};
// будем использовать следующую функцию для тестирования
function testexpr(expr) {
console.log('expression:', expr);
let ast = parse(expr);
// console.log('ast:', ast);
x.this = x;
let result = evaluate(ast, x);
console.log('result:', result);
console.log();
}
// примеры выражений
testexpr("serverId")
testexpr("id")
testexpr("types[0]")
testexpr("types[1]")
testexpr("params.voltage_5VSB_max.datetime")
testexpr("params.voltage_5VSB_max.value")
testexpr("params.voltage_5VSB_max")
testexpr("voltage_5VSB_max.datetime")
testexpr("voltage_5VSB_max.value")
testexpr("voltage_5VSB_max")
testexpr("pins.rs232Socket1")
testexpr("pins.rs232Socket1.params.port.datetime")
testexpr("pins.rs232Socket1.params.port.value")
testexpr("pins.rs232Socket1.params.port")
testexpr("pins.rs232Socket1.port")
testexpr("rs232Socket1")
testexpr("rs232Socket1.params.port.datetime")
testexpr("rs232Socket1.params.port.value")
testexpr("rs232Socket1.params.port")
testexpr("rs232Socket1.port")
testexpr("rs232Socket1.type")
testexpr("this.pins.rs232Socket1.type")
testexpr("this.rs232Socket1.type")
testexpr("this.pins.rs232Socket1.params.port.value")
testexpr("this.pins.rs232Socket1.params.port")
testexpr("this.pins.rs232Socket1.port")
testexpr("this.rs232Socket1.port")
testexpr("this.params.voltage_5VSB_max.datetime")
testexpr("this.params.voltage_5VSB_max")
testexpr("this.voltage_5VSB_max.value")
testexpr("this.voltage_5VSB_max")
testexpr("state == /^ok/")
testexpr("/^ok/ == state")
testexpr("/^alarm/ == state")
testexpr("/^ok/ === state")
expression: serverId
result: 8bc32339-08ee-4198-954f-fafbf4f2fd5b
expression: id
result: 023d7b3f-8293-4852-8c7d-60962efbacf8
expression: types[0]
result: Item
expression: types[1]
result: Computer
expression: params.voltage_5VSB_max.datetime
result: 1593228361
expression: params.voltage_5VSB_max.value
result: 5.5
expression: params.voltage_5VSB_max
result: 5.5
expression: voltage_5VSB_max.datetime
result: 1593228361
expression: voltage_5VSB_max.value
result: 5.5
expression: voltage_5VSB_max
result: 5.5
expression: pins.rs232Socket1
result: {
type: 'Rs232Socket',
params: {
port: {
value: 'COM1',
datetime: 1593228361,
status: null,
version: 25227518
}
}
}
expression: pins.rs232Socket1.params.port.datetime
result: 1593228361
expression: pins.rs232Socket1.params.port.value
result: COM1
expression: pins.rs232Socket1.params.port
result: COM1
expression: pins.rs232Socket1.port
result: COM1
expression: rs232Socket1
result: {
type: 'Rs232Socket',
params: {
port: {
value: 'COM1',
datetime: 1593228361,
status: null,
version: 25227518
}
}
}
expression: rs232Socket1.params.port.datetime
result: 1593228361
expression: rs232Socket1.params.port.value
result: COM1
expression: rs232Socket1.params.port
result: COM1
expression: rs232Socket1.port
result: COM1
expression: rs232Socket1.type
result: Rs232Socket
expression: this.pins.rs232Socket1.type
result: Rs232Socket
expression: this.rs232Socket1.type
result: Rs232Socket
expression: this.pins.rs232Socket1.params.port.value
result: COM1
expression: this.pins.rs232Socket1.params.port
result: COM1
expression: this.pins.rs232Socket1.port
result: COM1
expression: this.rs232Socket1.port
result: COM1
expression: this.params.voltage_5VSB_max.datetime
result: 1593228361
expression: this.params.voltage_5VSB_max
result: 5.5
expression: this.voltage_5VSB_max.value
result: 5.5
expression: this.voltage_5VSB_max
result: 5.5
expression: state == /^ok/
result: true
expression: /^ok/ == state
result: true
expression: /^alarm/ == state
result: false
expression: /^ok/ === state
result: false
jupyter-nbconvert --to HTML docs-static-eval.ipynb