@gabliam/expression
Version:
306 lines (305 loc) • 9.31 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Parser = exports.IS_STRING = void 0;
/* eslint-disable */
const escodegen_1 = require("escodegen");
const FAIL = Symbol('FAIL');
exports.IS_STRING = Symbol('IS_STRING');
class Parser {
constructor(ast) {
this.ast = ast;
}
parse(vars = {}) {
const result = this.walk(this.ast, vars);
return result === FAIL ? undefined : result;
}
parseUnary(node, vars) {
const val = this.walk(node.argument, vars);
switch (node.operator) {
case '+':
return +val;
case '-':
return -val;
case '~':
return ~val;
case '!':
return !val;
case 'typeof':
return typeof val;
case 'void':
case 'delete':
return undefined;
default:
return FAIL;
}
}
parseArray(node, vars) {
const xs = [];
for (const nodeElement of node.elements) {
const x = this.walk(nodeElement, vars);
if (x === FAIL) {
return FAIL;
}
xs.push(x);
}
return xs;
}
parseObject(node, vars) {
const obj = {};
for (let i = 0; i < node.properties.length; i++) {
const prop = node.properties[i];
const value = prop.value === null ? prop.value : this.walk(prop.value, vars);
if (value === FAIL) {
return FAIL;
}
const key = prop.key === null ? prop.key : this.walk(prop.key, vars);
if (key === FAIL) {
return FAIL;
}
obj[key] = value;
}
return obj;
}
parseLeftRight(node, vars) {
const l = this.walk(node.left, vars);
if (l === FAIL) {
return [FAIL];
}
const r = this.walk(node.right, vars);
if (r === FAIL) {
return [FAIL];
}
return [l, r];
}
parseBinary(node, vars) {
const [l, r] = this.parseLeftRight(node, vars);
if (l === FAIL) {
return FAIL;
}
if (null === l &&
undefined === r &&
node.left.raw &&
node.right.type === 'Identifier') {
return exports.IS_STRING;
}
switch (node.operator) {
case '==':
return l == r;
case '!=':
return l != r;
case '===':
return l === r;
case '!==':
return l !== r;
case '<':
return l < r;
case '<=':
return l <= r;
case '>':
return l > r;
case '>=':
return l >= r;
case '<<':
return l << r;
case '>>':
return l >> r;
case '>>>':
return l >>> r;
case '+':
return l + r;
case '-':
return l - r;
case '*':
return l * r;
case '/':
return l / r;
case '%':
return l % r;
case '**':
return l ** r;
case '|':
return l | r;
case '^':
return l ^ r;
case '&':
return l & r;
case 'in':
return l in r;
case 'instanceof':
return l instanceof r;
}
}
parseLogical(node, vars) {
const l = this.walk(node.left, vars);
if (l === FAIL) {
return FAIL;
}
if (l === false && node.operator === '&&') {
return false;
}
const r = this.walk(node.right, vars);
switch (node.operator) {
case '||':
return l || r;
case '&&':
return l && r;
}
}
parseIdentifier(node, vars) {
if ({}.hasOwnProperty.call(vars, node.name)) {
return vars[node.name];
}
if (global.hasOwnProperty(node.name)) {
return global[node.name];
}
return undefined;
}
parseThis(node, vars) {
if ({}.hasOwnProperty.call(vars, 'this')) {
return vars['this'];
}
return FAIL;
}
parseCall(node, vars) {
const callee = this.walk(node.callee, vars);
if (callee === FAIL) {
return FAIL;
}
if (typeof callee !== 'function') {
return FAIL;
}
let ctx = node.callee.object
? this.walk(node.callee.object, vars)
: FAIL;
if (ctx === FAIL) {
ctx = null;
}
const args = [];
for (const arg of node.arguments) {
const x = this.walk(arg, vars);
if (x === FAIL) {
return FAIL;
}
args.push(x);
}
return callee.apply(ctx, args);
}
parseMember(node, vars) {
const obj = this.walk(node.object, vars);
if (obj === undefined) {
return undefined;
}
if (obj === FAIL) {
return FAIL;
}
if (node.property.type === 'Identifier') {
return obj[node.property.name];
}
const prop = this.walk(node.property, vars);
if (prop === FAIL) {
return FAIL;
}
return obj[prop];
}
parseConditional(node, vars) {
const val = this.walk(node.test, vars);
if (val === FAIL) {
return FAIL;
}
return val
? this.walk(node.consequent, vars)
: this.walk(node.alternate, vars);
}
parseStatement(node, vars) {
const val = this.walk(node.expression, vars);
if (val === FAIL) {
return FAIL;
}
return val;
}
parseReturnStatement(node, vars) {
return this.walk(node.argument, vars);
}
parseFunction(node, vars) {
const bodies = node.body.body;
const newVars = Object.assign({}, vars);
node.params.forEach((key) => {
if (key.type === 'Identifier') {
newVars[key.name] = null;
}
});
for (const i in bodies) {
if (this.walk(bodies[i], newVars) === FAIL) {
return FAIL;
}
}
const keys = Object.keys(vars);
const vals = keys.map((key) => {
return vars[key];
});
// eslint-disable-next-line prefer-spread
return Function(keys.join(', '), 'return ' + (0, escodegen_1.generate)(node)).apply(null, vals);
}
parseTemplateLiteral(node, vars) {
let str = '';
let i;
for (i = 0; i < node.expressions.length; i++) {
str += this.walk(node.quasis[i], vars);
str += this.walk(node.expressions[i], vars);
}
str += this.walk(node.quasis[i], vars);
return str;
}
parseTaggedTemplate(node, vars) {
const tag = this.walk(node.tag, vars);
const quasi = node.quasi;
const strings = quasi.quasis.map((q) => this.walk(q, vars));
const values = quasi.expressions.map((e) => this.walk(e, vars));
// eslint-disable-next-line prefer-spread
return tag.apply(null, [strings].concat(values));
}
walk(node, vars) {
if (!node) {
return FAIL;
}
switch (node.type) {
case 'Literal':
return node.value;
case 'UnaryExpression':
return this.parseUnary(node, vars);
case 'ArrayExpression':
return this.parseArray(node, vars);
case 'ObjectExpression':
return this.parseObject(node, vars);
case 'BinaryExpression':
return this.parseBinary(node, vars);
case 'LogicalExpression':
return this.parseLogical(node, vars);
case 'Identifier':
return this.parseIdentifier(node, vars);
case 'ThisExpression':
return this.parseThis(node, vars);
case 'CallExpression':
return this.parseCall(node, vars);
case 'MemberExpression':
return this.parseMember(node, vars);
case 'ConditionalExpression':
return this.parseConditional(node, vars);
case 'ExpressionStatement':
return this.parseStatement(node, vars);
case 'ReturnStatement':
return this.parseReturnStatement(node, vars);
case 'FunctionExpression':
return this.parseFunction(node, vars);
case 'TemplateLiteral':
return this.parseTemplateLiteral(node, vars);
case 'TaggedTemplateExpression':
return this.parseTaggedTemplate(node, vars);
case 'TemplateElement':
return node.value.cooked;
default:
return FAIL;
}
}
}
exports.Parser = Parser;