nimma
Version:
Scalable JSONPath engine.
520 lines (468 loc) • 15.1 kB
JavaScript
'use strict';
var builders = require('../ast/builders.cjs');
var guards = require('../guards.cjs');
var internalScope = require('../templates/internal-scope.cjs');
var sandbox = require('../templates/sandbox.cjs');
var scope = require('../templates/scope.cjs');
var state = require('../templates/state.cjs');
function generateStateCheck(branch, iterator, check, deep) {
if (iterator.feedback.fixed) {
return generateFixedStateCheck(branch, iterator, check);
}
const [prevNo, no] = iterator.state.numbers;
if (iterator.state.isLastNode) {
return builders.ifStatement(
builders.logicalExpression(
'||',
builders.binaryExpression('<', state.default.initialValue, builders.numericLiteral(prevNo)),
builders.unaryExpression('!', check),
),
builders.returnStatement(),
);
}
return builders.ifStatement(
builders.binaryExpression('>=', state.default.initialValue, builders.numericLiteral(prevNo)),
builders.blockStatement([
builders.ifStatement(
check,
builders.blockStatement([
builders.assignmentExpression('|=', state.default.value, builders.numericLiteral(no)),
]),
deep
? void 0
: builders.ifStatement(
builders.binaryExpression(
'===',
builders.callExpression(
builders.memberExpression(builders.identifier('state'), builders.identifier('at')),
[builders.numericLiteral(-1)],
),
builders.numericLiteral(prevNo),
),
builders.blockStatement([
builders.assignmentExpression(
'&=',
state.default.value,
builders.numericLiteral(iterator.state.groupNumbers[0]),
),
builders.returnStatement(),
]),
),
),
]),
);
}
function generateFixedStateCheck(branch, iterator, check) {
const [prevNo, no] = iterator.state.numbers;
if (iterator.state.isLastNode) {
return builders.ifStatement(
builders.logicalExpression(
'||',
builders.binaryExpression('!==', state.default.initialValue, builders.numericLiteral(prevNo)),
builders.logicalExpression(
'||',
builders.binaryExpression(
'!==',
scope.default.depth,
builders.numericLiteral(iterator.state.absoluteOffset + 1),
),
builders.unaryExpression('!', check),
),
),
builders.returnStatement(),
);
}
return builders.ifStatement(
builders.logicalExpression(
'&&',
builders.binaryExpression('===', state.default.initialValue, builders.numericLiteral(prevNo)),
builders.binaryExpression(
'===',
scope.default.depth,
builders.numericLiteral(iterator.state.absoluteOffset + 1),
),
),
builders.blockStatement([
builders.ifStatement(
check,
builders.blockStatement([
builders.assignmentExpression('=', state.default.value, builders.numericLiteral(no)),
]),
),
]),
);
}
function generatePropertyAccess(iterator) {
if (iterator.feedback.fixed) {
return builders.memberExpression(
scope.default.path,
builders.numericLiteral(iterator.state.absoluteOffset),
true,
);
}
return (!iterator.state.indexed && iterator.state.isLastNode) ||
iterator.state.usesState
? scope.default.property
: builders.memberExpression(
scope.default.path,
iterator.state.indexed
? builders.numericLiteral(iterator.state.offset)
: builders.binaryExpression(
'-',
scope.default.depth,
builders.numericLiteral(Math.abs(iterator.state.offset)),
),
true,
);
}
function generateMemberExpression(branch, iterator, node) {
if (iterator.state.usesState) {
branch.push(
generateStateCheck(
branch,
iterator,
builders.safeBinaryExpression(
'===',
generatePropertyAccess(iterator),
builders.literal(node.value),
),
node.deep,
),
);
} else {
branch.push(
builders.ifStatement(
builders.safeBinaryExpression(
'!==',
generatePropertyAccess(iterator),
builders.literal(node.value),
),
builders.returnStatement(),
),
);
}
}
function generateMultipleMemberExpression(branch, iterator, node) {
const property = generatePropertyAccess(iterator);
const logicalOperator = iterator.state.usesState ? '||' : '&&';
const binaryOperator = iterator.state.usesState ? '===' : '!==';
const condition = node.value
.slice(1)
.reduce(
(concat, member) =>
builders.logicalExpression(
logicalOperator,
concat,
builders.safeBinaryExpression(binaryOperator, property, builders.literal(member)),
),
builders.safeBinaryExpression(
binaryOperator,
property,
builders.literal(node.value[0]),
),
);
if (iterator.state.usesState) {
branch.push(generateStateCheck(branch, iterator, condition, node.deep));
} else {
branch.push(builders.ifStatement(condition, builders.returnStatement()));
}
}
const IN_BOUNDS_IDENTIFIER = builders.identifier('inBounds');
function generateNegativeSliceExpression(branch, iterator, node, tree) {
tree.addRuntimeDependency(IN_BOUNDS_IDENTIFIER.name);
const property = generatePropertyAccess(iterator);
const isNumberBinaryExpression = builders.binaryExpression(
'!==',
builders.unaryExpression('typeof', property),
builders.stringLiteral('number'),
);
const condition = builders.binaryExpression(
'||',
isNumberBinaryExpression,
builders.unaryExpression(
'!',
builders.callExpression(IN_BOUNDS_IDENTIFIER, [
scope.default.sandbox,
generatePropertyAccess(iterator),
...node.value.map(value => builders.numericLiteral(value)),
]),
),
);
if (iterator.state.usesState) {
branch.push(
generateStateCheck(
branch,
iterator,
builders.unaryExpression('!', condition),
node.deep,
),
);
} else {
branch.push(builders.ifStatement(condition, builders.returnStatement()));
}
}
function generateSliceExpression(branch, iterator, node, tree) {
if (guards.isNegativeSliceExpression(node)) {
return generateNegativeSliceExpression(branch, iterator, node, tree);
}
const property = generatePropertyAccess(iterator);
const isNumberBinaryExpression = builders.binaryExpression(
'!==',
builders.unaryExpression('typeof', property),
builders.stringLiteral('number'),
);
const condition = node.value.reduce((merged, value, i) => {
if (i === 0 && value === 0) {
return merged;
}
if (i === 1 && !Number.isFinite(value)) {
return merged;
}
if (i === 2 && value === 1) {
return merged;
}
const operator = i === 0 ? '<' : i === 1 ? '>=' : '%';
const expression = builders.binaryExpression(
operator,
property,
builders.numericLiteral(Number(value)),
);
return builders.logicalExpression(
'||',
merged,
operator === '%'
? builders.logicalExpression(
'&&',
builders.binaryExpression(
'!==',
property,
builders.numericLiteral(node.value[0]),
),
builders.binaryExpression(
'!==',
expression,
builders.numericLiteral(node.value[0]),
),
)
: expression,
);
}, isNumberBinaryExpression);
if (iterator.state.usesState) {
branch.push(
generateStateCheck(
branch,
iterator,
builders.unaryExpression('!', condition),
node.deep,
),
);
} else {
branch.push(builders.ifStatement(condition, builders.returnStatement()));
}
}
function generateWildcardExpression(branch, iterator) {
if (!iterator.state.usesState || iterator.feedback.fixed) return;
const [prevNo, no] = iterator.state.numbers;
if (iterator.state.isLastNode) {
branch.push(
builders.ifStatement(
builders.binaryExpression('<', state.default.initialValue, builders.numericLiteral(prevNo)),
builders.returnStatement(),
),
);
} else {
branch.push(
builders.ifStatement(
builders.binaryExpression('>=', state.default.initialValue, builders.numericLiteral(prevNo)),
builders.blockStatement([
builders.assignmentExpression('|=', state.default.value, builders.numericLiteral(no)),
]),
),
);
}
}
function generateCustomShorthandExpression(branch, iterator, node) {
branch.push(
builders.ifStatement(
builders.unaryExpression(
'!',
builders.callExpression(
builders.memberExpression(
internalScope.default.shorthands,
builders.identifier(node.value),
),
iterator.state.usesState
? [scope.default._, state.default._, builders.numericLiteral(iterator.state.numbers[0])]
: [scope.default._],
),
),
builders.returnStatement(),
),
);
}
function generateFilterScriptExpression(
branch,
iterator,
{ value: esTree },
tree,
) {
assertDefinedIdentifier(esTree);
const node = rewriteESTree(tree, esTree);
if (iterator.state.usesState) {
branch.push(
generateStateCheck(branch, iterator, node, iterator.state.isLastNode),
);
} else {
branch.push(
builders.ifStatement(builders.unaryExpression('!', node), builders.returnStatement()),
);
}
}
function rewriteESTree(tree, node) {
switch (node.type) {
case 'LogicalExpression':
case 'BinaryExpression':
if (node.operator === 'in') {
node.operator = '===';
node.left = builders.callExpression(
builders.memberExpression(node.right, builders.identifier('includes')),
[rewriteESTree(tree, node.left)],
);
node.right = builders.booleanLiteral(true);
} else if (node.operator === '~=') {
if (node.right.type !== 'Literal') {
throw SyntaxError('~= must be used with strings');
}
return builders.callExpression(
builders.memberExpression(
builders.regExpLiteral(node.right.value, ''),
builders.identifier('test'),
),
[rewriteESTree(tree, node.left)],
);
} else {
node.left = rewriteESTree(tree, node.left);
node.right = rewriteESTree(tree, node.right);
assertDefinedIdentifier(node.left);
assertDefinedIdentifier(node.right);
}
break;
case 'UnaryExpression':
node.argument = rewriteESTree(tree, node.argument);
assertDefinedIdentifier(node.argument);
return node;
case 'MemberExpression':
node.object = rewriteESTree(tree, node.object);
assertDefinedIdentifier(node.object);
node.property = rewriteESTree(tree, node.property);
if (node.computed) {
assertDefinedIdentifier(node.property);
}
break;
case 'CallExpression':
if (
node.callee.type === 'Identifier' &&
node.callee.name.startsWith('@')
) {
return processAtIdentifier(tree, node.callee.name);
}
node.callee = rewriteESTree(tree, node.callee);
node.arguments = node.arguments.map(argument =>
rewriteESTree(tree, argument),
);
if (
node.callee.type === 'MemberExpression' &&
node.callee.object === sandbox.default.property &&
node.callee.property.name in String.prototype
) {
node.callee.object = builders.callExpression(builders.identifier('String'), [
node.callee.object,
]);
}
assertDefinedIdentifier(node.callee);
break;
case 'Identifier':
if (node.name.startsWith('@')) {
return processAtIdentifier(tree, node.name);
}
if (node.name === 'undefined') {
return builders.unaryExpression('void', builders.numericLiteral(0));
}
if (node.name === 'index') {
return sandbox.default.index;
}
break;
}
return node;
}
function processAtIdentifier(tree, name) {
switch (name) {
case '@':
return sandbox.default.value;
case '@root':
return sandbox.default.root;
case '@path':
return sandbox.default.path;
case '@property':
return sandbox.default.property;
case '@parent':
return sandbox.default.parentValue;
case '@parentProperty':
return sandbox.default.parentProperty;
case '@string':
case '@number':
case '@boolean':
return builders.binaryExpression(
'===',
builders.unaryExpression('typeof', sandbox.default.value),
builders.stringLiteral(name.slice(1)),
);
case '@scalar':
return builders.logicalExpression(
'||',
builders.binaryExpression('===', sandbox.default.value, builders.nullLiteral()),
builders.binaryExpression(
'!==',
builders.unaryExpression('typeof', sandbox.default.value),
builders.stringLiteral('object'),
),
);
case '@array':
return builders.callExpression(
builders.memberExpression(builders.identifier('Array'), builders.identifier('isArray')),
[sandbox.default.value],
);
case '@null':
return builders.binaryExpression('===', sandbox.default.value, builders.nullLiteral());
case '@object':
return builders.logicalExpression(
'&&',
builders.binaryExpression('!==', sandbox.default.value, builders.nullLiteral()),
builders.binaryExpression(
'===',
builders.unaryExpression('typeof', sandbox.default.value),
builders.stringLiteral('object'),
),
);
case '@integer':
return builders.callExpression(
builders.memberExpression(builders.identifier('Number'), builders.identifier('isInteger')),
[sandbox.default.value],
);
default:
throw Error(`Unsupported shorthand "${name}"`);
}
}
const KNOWN_IDENTIFIERS = [scope.default._.name, 'index'];
function assertDefinedIdentifier(node) {
if (node.type !== 'Identifier') return;
if (KNOWN_IDENTIFIERS.includes(node.name)) return;
throw ReferenceError(`"${node.name}" is not defined`);
}
exports.generateCustomShorthandExpression = generateCustomShorthandExpression;
exports.generateFilterScriptExpression = generateFilterScriptExpression;
exports.generateMemberExpression = generateMemberExpression;
exports.generateMultipleMemberExpression = generateMultipleMemberExpression;
exports.generateSliceExpression = generateSliceExpression;
exports.generateWildcardExpression = generateWildcardExpression;
exports.rewriteESTree = rewriteESTree;