DockerERTFF/lib/core/parser.js

752 lines
20 KiB
JavaScript

const fs = require('fs-extra');
const path = require('path');
const { randomUUID } = require('crypto');
const { v4: uuidv4 } = require('uuid');
const NOOP = function() {};
const OPERATORS = {
'{' : 'L_CURLY',
'}' : 'R_CURLY',
'(' : 'L_PAREN',
')' : 'R_PAREN',
'[' : 'L_BRACKET',
']' : 'R_BRACKET',
'.' : 'PERIOD',
',' : 'COMMA',
':' : 'COLON',
'?' : 'QUESTION',
// Arithmetic operators
'+' : 'ADDICTIVE',
'-' : 'ADDICTIVE',
'*' : 'MULTIPLICATIVE',
'/' : 'MULTIPLICATIVE',
'%' : 'MULTIPLICATIVE',
// Comparison operators
'===': 'EQUALITY',
'!==': 'EQUALITY',
'==' : 'EQUALITY',
'!=' : 'EQUALITY',
'<' : 'RELATIONAL',
'>' : 'RELATIONAL',
'<=' : 'RELATIONAL',
'>=' : 'RELATIONAL',
'in' : 'RELATIONAL',
// Logical operators
'&&' : 'LOGICAL_AND',
'||' : 'LOGICAL_OR',
'!' : 'LOGICAL_NOT',
// Bitwise operators
'&' : 'BITWISE_AND',
'|' : 'BITWISE_OR',
'^' : 'BITWISE_XOR',
'~' : 'BITWISE_NOT',
'<<' : 'BITWISE_SHIFT',
'>>' : 'BITWISE_SHIFT',
'>>>': 'BITWISE_SHIFT'
};
const EXPRESSIONS = {
'in' : function(a, b) { return a() in b(); },
'?' : function(a, b, c) { return a() ? b() : c(); },
'+' : function(a, b) { a = a(); b = b(); return a == null ? b : b == null ? a : a + b; },
'-' : function(a, b) { return a() - b(); },
'*' : function(a, b) { return a() * b(); },
'/' : function(a, b) { return a() / b(); },
'%' : function(a, b) { return a() % b(); },
'===': function(a, b) { return a() === b(); },
'!==': function(a, b) { return a() !== b(); },
'==' : function(a, b) { return a() == b(); },
'!=' : function(a, b) { return a() != b(); },
'<' : function(a, b) { return a() < b(); },
'>' : function(a, b) { return a() > b(); },
'<=' : function(a, b) { return a() <= b(); },
'>=' : function(a, b) { return a() >= b(); },
'&&' : function(a, b) { return a() && b(); },
'||' : function(a, b) { return a() || b(); },
'&' : function(a, b) { return a() & b(); },
'|' : function(a, b) { return a() | b(); },
'^' : function(a, b) { return a() ^ b(); },
'<<' : function(a, b) { return a() << b(); },
'>>' : function(a, b) { return a() >> b(); },
'>>>': function(a, b) { return a() >>> b(); },
'~' : function(a) { return ~a(); },
'!' : function(a) { return !a(); }
};
const ESCAPE = {
'n': '\n',
'f': '\f',
'r': '\r',
't': '\t',
'v': '\v',
"'": "'",
'"': '"',
'`': '`'
};
const formatters = require('../formatters');
// User formatters
if (fs.existsSync('extensions/server_connect/formatters')) {
const files = fs.readdirSync('extensions/server_connect/formatters');
for (let file of files) {
if (path.extname(file) == '.js') {
Object.assign(formatters, require(`../../extensions/server_connect/formatters/${file}`));
}
}
}
function lexer(expr) {
let tokens = [],
token,
name,
start,
index = 0,
op = true,
ch,
ch2,
ch3;
while (index < expr.length) {
start = index;
ch = read();
if (isQuote(ch) && op) {
name = 'STRING';
token = readString(ch);
op = false;
} else if ((isDigid(ch) || (is('.') && peek() && isDigid(peek()))) && op) {
name = 'NUMBER';
token = readNumber();
op = false;
} else if (isAlpha(ch) && op) {
name = 'IDENT';
token = readIdent();
if (is('(')) {
name = 'METHOD';
}
op = false;
} else if (is('/') && op && (token == '(' || token == ',' || token == '?' || token == ':') && testRegexp()) {
name = 'REGEXP';
token = readRegexp();
op = false;
} else if (isWhitespace(ch)) {
index++;
continue;
} else if ((ch3 = read(3)) && OPERATORS[ch3]) {
name = OPERATORS[ch3];
token = ch3;
op = true;
index += 3;
} else if ((ch2 = read(2)) && OPERATORS[ch2]) {
name = OPERATORS[ch2];
token = ch2;
op = true;
index += 2;
} else if (OPERATORS[ch]) {
name = OPERATORS[ch];
token = ch;
op = true;
index++;
} else {
throw new Error('Lexer Error: Unexpected token "' + ch + '" at column ' + index + ' in expression {{' + expr + '}}');
}
tokens.push({
name: name,
index: start,
value: token
});
}
return tokens;
function read(n) {
if (!n) n = 1;
return expr.substr(index, n);
}
function peek(n) {
n = n || 1;
return index + n < expr.length ? expr[index + n] : false;
}
function is(chars) {
return chars.indexOf(ch) != -1;
}
function isQuote(ch) {
return ch == '"' || ch == "'";
}
function isDigid(ch) {
return ch >= '0' && ch <= '9';
}
function isAlpha(ch) {
return (ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
ch == '_' || ch == '$';
}
function isAlphaNum(ch) {
return isAlpha(ch) || isDigid(ch);
}
function isWhitespace(ch) {
return ch == ' ' || ch == '\r' || ch == '\t' || ch == '\n' || ch == '\v' || ch == '\u00A0';
}
function isExpOperator(ch) {
return ch == '-' || ch == '+' || isDigid(ch);
}
function readString(quote) {
let str = '', esc = false;
index++;
while (index < expr.length) {
ch = read();
if (esc) {
if (ch == 'u') {
// unicode escape
index++;
let hex = read(4);
if (!hex.match(/[\da-f]{4}/i)) {
throw new Error('Lexer Error: Invalid unicode escape at column ' + index + ' in expression {{' + expr + '}}');
}
str += String.fromCharCode(parseInt(hex, 16));
index += 3;
} else {
str += ESCAPE[ch] ? ESCAPE[ch] : ch;
}
esc = false;
} else if (ch == '\\') {
// escape character
esc = true;
} else if (ch == quote) {
// end of string
index ++;
return str;
} else {
str += ch;
}
index++;
}
throw new Error('Lexer Error: Unterminated string at column ' + index + ' in expression {{' + expr + '}}');
}
function readNumber() {
let num = '', exp = false;
while (index < expr.length) {
ch = read();
if (isDigid(ch) || (is('.') && peek() && isDigid(peek()))) {
num += ch;
} else {
let next = peek();
if (is('eE') && isExpOperator(next)) {
num += 'e';
exp = true;
} else if (isExpOperator(ch) && next && isDigid(next) && exp) {
num += ch;
exp = false;
} else if (isExpOperator(ch) && (!next || !isDigid(next)) && exp) {
throw new Error('Lexer Error: Invalid exponent at column ' + index + ' in expression {{' + expr + '}}');
} else {
break;
}
}
index++;
}
return +num;
}
function readIdent() {
let ident = '';
while (index < expr.length) {
ch = read();
if (isAlphaNum(ch)) {
ident += ch;
} else {
break;
}
index++;
}
return ident;
}
function readRegexp() {
let re = '', mod = '', esc = false;
index ++;
while (index < expr.length) {
ch = read();
if (esc) {
esc = false;
} else if (ch == '\\') {
esc = true;
} else if (ch == '/') {
index++;
while ('ign'.indexOf(ch = read()) != -1) {
mod += ch;
index++;
}
return re + '%%%' + mod;
}
re += ch;
index++;
}
throw new Error('Lexer Error: Unterminated regexp at column ' + index + ' in expression {{' + expr + '}}');
}
function testRegexp() {
var idx = index, ok = true;
try {
readRegexp();
} catch (e) {
ok = false;
}
// reset our index and ch
index = idx;
ch = '/';
return ok;
}
}
function parser(expr, scope) {
let tokens = lexer(expr),
context = undefined,
RESERVED = {
'PI' : function() { return Math.PI; },
'UUID' : function() { return randomUUID ? randomUUID() : uuidv4(); },
'NOW' : function() { return date(); },
'NOW_UTC' : function() { return utc_date(); },
'TIMESTAMP': function() { return timestamp(); },
'$this' : function() { return scope.data; },
'$global' : function() { return globalScope.data; },
'$parent' : function() { return scope.parent && scope.parent.data; },
'null' : function() { return null; },
'true' : function() { return true; },
'false' : function() { return false; },
'_' : function() { return { __dmxScope__: true } }
};
return start()();
function pad(s, n) {
return ('000' + s).substr(-n);
}
function date(dt) {
dt = dt || new Date();
return pad(dt.getFullYear(), 4) + '-' + pad(dt.getMonth() + 1, 2) + '-' + pad(dt.getDate(), 2) + ' ' +
pad(dt.getHours(), 2) + ':' + pad(dt.getMinutes(), 2) + ':' + pad(dt.getSeconds(), 2);
}
function utc_date(dt) {
dt = dt || new Date();
return pad(dt.getUTCFullYear(), 4) + '-' + pad(dt.getUTCMonth() + 1, 2) + '-' + pad(dt.getUTCDate(), 2) + 'T' +
pad(dt.getUTCHours(), 2) + ':' + pad(dt.getUTCMinutes(), 2) + ':' + pad(dt.getUTCSeconds(), 2) + 'Z';
}
function timestamp(dt) {
dt = dt || new Date();
return ~~(dt / 1000);
}
function read() {
if (tokens.length === 0) {
throw new Error('Parser Error: Unexpected end of expression {{' + expr + '}}');
}
return tokens[0];
}
function peek(e) {
if (tokens.length > 0) {
let token = tokens[0];
if (!e || token.name == e) {
return token;
}
}
return false;
}
function expect(e) {
let token = peek(e);
if (token) {
tokens.shift();
return token;
}
return false;
}
function consume(e) {
if (!expect(e)) {
throw new Error('Parser Error: Unexpected token, expecting ' + e + ' in expression {{' + expr + '}}');
}
}
function fn(expr) {
let args = [].slice.call(arguments, 1);
return function() {
if (EXPRESSIONS[expr]) {
return EXPRESSIONS[expr].apply(context, args);
} else {
return expr;
}
}
}
function start() {
return conditional();
}
function conditional() {
let left = logicalOr(), middle, token;
if ((token = expect('QUESTION'))) {
middle = conditional();
if ((token = expect('COLON'))) {
return fn('?', left, middle, conditional());
} else {
throw new Error('Parse Error: Expecting : in expression {{' + expr + '}}');
}
} else {
return left;
}
}
function logicalOr() {
let left = logicalAnd(), token;
while (true) {
if ((token = expect('LOGICAL_OR'))) {
left = fn(token.value, left, logicalAnd());
} else {
return left;
}
}
}
function logicalAnd() {
let left = bitwiseOr(), token;
if ((token = expect('LOGICAL_AND'))) {
left = fn(token.value, left, logicalAnd());
}
return left;
}
function bitwiseOr() {
let left = bitwiseXor(), token;
if ((token = expect('BITWISE_OR'))) {
left = fn(token.value, left, bitwiseXor());
}
return left;
}
function bitwiseXor() {
let left = bitwiseAnd(), token;
if ((token = expect('BITWISE_XOR'))) {
left = fn(token.value, left, bitwiseAnd());
}
return left;
}
function bitwiseAnd() {
let left = equality(), token;
if ((token = expect('BITWISE_AND'))) {
left = fn(token.value, left, bitwiseAnd());
}
return left;
}
function equality() {
let left = relational(), token;
if ((token = expect('EQUALITY'))) {
left = fn(token.value, left, equality());
}
return left;
}
function relational() {
let left = bitwiseShift(), token;
if ((token = expect('RELATIONAL'))) {
left = fn(token.value, left, relational());
}
return left;
}
function bitwiseShift() {
let left = addictive(), token;
if ((token = expect('BITWISE_SHIFT'))) {
left = fn(token.value, left, addictive());
}
return left;
}
function addictive() {
let left = multiplicative(), token;
while ((token = expect('ADDICTIVE'))) {
left = fn(token.value, left, multiplicative());
}
return left;
}
function multiplicative() {
let left = unary(), token;
while ((token = expect('MULTIPLICATIVE'))) {
left = fn(token.value, left, unary());
}
return left;
}
function unary() {
let token;
if ((token = expect('ADDICTIVE'))) {
if (token.value == '+') {
return primary();
} else {
return fn(token.value, function() { return 0; }, unary());
}
} else if ((token = expect('LOGICAL_NOT'))) {
return fn(token.value, unary());
}
return primary();
}
function primary() {
let value, next;
if (expect('L_PAREN')) {
value = start();
consume('R_PAREN');
} else if (expect('L_CURLY')) {
let obj = {};
if (read().name != 'R_CURLY') {
do {
let key = expect().value;
consume('COLON');
obj[key] = start()();
} while (expect('COMMA'));
}
value = fn(obj);
consume('R_CURLY');
} else if (expect('L_BRACKET')) {
let arr = [];
if (read().name != 'R_BRACKET') {
do {
arr.push(start()());
} while (expect('COMMA'));
}
value = fn(arr);
consume('R_BRACKET');
} else if (expect('PERIOD')) {
value = peek() ? objectMember(fn(scope.data)) : fn(scope.data);
} else {
let token = expect();
if (token === false) {
throw new Error('Parser Error: Not a primary expression {{' + expr + '}}');
}
if (token.name == 'IDENT') {
value = RESERVED.hasOwnProperty(token.value)
? RESERVED[token.value]
: function() { return scope.get(token.value) };
} else if (token.name == 'METHOD') {
if (!formatters[token.value]) {
throw new Error('Parser Error: Formatter "' + token.value + '" does not exist, expression {{' + expression + '}}');
}
value = fn(formatters[token.value]);
} else if (token.name == 'REGEXP') {
value = function() {
let re = token.value.split('%%%');
return new RegExp(re[0], re[1]);
};
} else {
value = function() { return token.value };
}
}
while ((next = expect('L_PAREN') || expect('L_BRACKET') || expect('PERIOD'))) {
if (next.value == '(') {
value = functionCall(value, context);
} else if (next.value == '[') {
value = objectIndex(value);
} else if (next.value == '.') {
context = value;
value = objectMember(value);
} else {
throw new Error('Parser Error: Parse error in expression {{' + expr + '}}');
}
}
context = undefined;
return value;
}
function functionCall(func, ctx) {
let argsFn = [];
if (read().name != 'R_PAREN') {
do {
argsFn.push(start());
} while (expect('COMMA'));
}
consume('R_PAREN');
return function() {
let args = [];
if (ctx) args.push(ctx());
for (let argFn of argsFn) {
args.push(argFn());
}
let fnPtr = func() || NOOP;
return fnPtr.apply(null, args);
}
}
function objectIndex(obj) {
let indexFn = start();
consume('R_BRACKET');
return function() {
let o = obj(),
i = indexFn();
if (typeof o != 'object') return undefined;
if (o.__dmxScope__) {
return scope.get(i);
}
return o[i];
}
}
function objectMember(obj) {
let token = expect();
return function() {
let o = obj();
if (token.name == 'METHOD') {
if (!formatters[token.value]) {
throw new Error('Parser Error: Formatter "' + token.value + '" does not exist, expression {{' + expr + '}}');
}
return formatters[token.value];
}
if (o && o.__dmxScope) {
return scope.get(token.value);
}
return o && o[token.value];
}
}
}
function parseValue(value, scope) {
if (value == null) return value;
value = value.valueOf();
if (typeof value == 'object') {
for (let key in value) {
value[key] = parseValue(value[key], scope);
}
}
if (typeof value == 'string') {
if (value.substr(0, 2) == '{{' && value.substr(-2) == '}}') {
let expr = value.replace(/^\{\{|\}\}$/g, '');
if (expr.indexOf('{{') == -1) {
return parser(expr, scope);
}
}
return parseTemplate(value, scope);
}
return value;
}
function parseTemplate(template, scope) {
return template.replace(/\{\{(.*?)\}\}/g, function(a, m) {
var value = parser(m, scope);
return value != null ? String(value) : '';
});
}
exports.lexer = lexer;
exports.parse = parser;
exports.parseValue = parseValue;
exports.parseTemplate = parseTemplate;