UNPKG

nimma

Version:

Scalable JSONPath engine.

520 lines (468 loc) 15.1 kB
'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;