UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

113 lines 5.61 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processAsNamedCall = processAsNamedCall; exports.processChainedCall = processChainedCall; const named_call_handling_1 = require("./functions/call/named-call-handling"); const make_argument_1 = require("./functions/call/argument/make-argument"); const process_argument_1 = require("./functions/process-argument"); const type_1 = require("../../../r-bridge/lang-4.x/ast/model/type"); const r_function_call_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call"); const r_argument_1 = require("../../../r-bridge/lang-4.x/ast/model/nodes/r-argument"); const assert_1 = require("../../../util/assert"); /** * Helper function for {@link processNamedCall} using the given `functionName` as the name of the function. */ function processAsNamedCall({ info, lexeme, location }, data, name, args) { return (0, named_call_handling_1.processNamedCall)({ type: type_1.RType.Symbol, info, content: name, lexeme, location, }, (0, make_argument_1.wrapArgumentsUnnamed)(args, data.completeAst.idMap), info.id, data); } function isBinaryOpOrPipe(node) { return node.type === type_1.RType.BinaryOp || node.type === type_1.RType.Pipe; } function isInfixSpecialCall(node) { return node.type === type_1.RType.FunctionCall && node.named === true && node.infixSpecial === true && node.arguments.length === 2; } function isChainNode(node) { return node !== undefined && (isBinaryOpOrPipe(node) || isInfixSpecialCall(node)); } /** * The raw (unwrapped) left operand of a chain node, used to detect whether the chain continues. * <p> * Unlike {@link RType#BinaryOp}, whose `lhs` is the raw operand, both parser backends always pre-wrap a * {@link RType#Pipe}'s `lhs` (and an infix-special {@link RType#FunctionCall}'s first argument) as an * {@link RArgument}, so those need unwrapping via `.value` first. */ function chainLhsRaw(node) { if (node.type === type_1.RType.FunctionCall) { const arg = node.arguments[0]; return arg === r_function_call_1.EmptyArgument ? undefined : arg.value; } return r_argument_1.RArgument.is(node.lhs) ? node.lhs.value : node.lhs; } /** The left operand of a chain node, wrapped as the {@link RArgument} that {@link processAllArguments} would process it as. */ function chainLhsArgument(node, idMap) { if (node.type === type_1.RType.FunctionCall) { const arg = node.arguments[0]; (0, assert_1.guard)(arg !== r_function_call_1.EmptyArgument, 'an infix-special call always has a non-empty first argument'); return arg; } if (r_argument_1.RArgument.is(node.lhs)) { return node.lhs; } const wrapped = (0, make_argument_1.toUnnamedArgument)(node.lhs, idMap); (0, assert_1.guard)(wrapped !== r_function_call_1.EmptyArgument, 'the lhs of a binary operator is never empty'); return wrapped; } /** * Processes a single chain node (see {@link ChainNode}), recursing normally into its children. A caller folding * a whole chain (see {@link processChainedCall}) may pass an already-processed left operand via * `data.precomputedFirstArg` (keyed by `node.info.id`) to avoid recursing into it again. */ function processSingleChainNode(node, data) { if (node.type === type_1.RType.FunctionCall) { return (0, named_call_handling_1.processNamedCall)(node.functionName, node.arguments, node.info.id, data); } const { info, lexeme, location, lhs, rhs } = node; return (0, named_call_handling_1.processNamedCall)({ type: type_1.RType.Symbol, info, content: node.type === type_1.RType.Pipe ? lexeme : node.operator, lexeme, location, }, (0, make_argument_1.wrapArgumentsUnnamed)([lhs, rhs], data.completeAst.idMap), info.id, data); } /** * Processes a {@link ChainNode}: {@link RType#BinaryOp}, {@link RType#Pipe}, and magrittr-style `%op%` calls * (infix-special {@link RType#FunctionCall}s) all nest on their left operand, so a long `a + b + c + ...` or * `a %>% f() %>% f() %>% ...` chain buries it arbitrarily deep. * This walks the left spine of chain nodes with an explicit loop (bounded stack usage regardless of * chain length), processes the innermost (leftmost) node normally, and then folds outward: each level's left * operand is wrapped via {@link processFunctionArgument}. */ function processChainedCall(node, data) { if (!isChainNode(chainLhsRaw(node))) { return processSingleChainNode(node, data); } const spine = [node]; let cur = chainLhsRaw(node); while (true) { spine.push(cur); const next = chainLhsRaw(cur); if (!isChainNode(next)) { break; } cur = next; } // process the innermost (leftmost) node normally, then fold outward through the rest of the spine let result = processSingleChainNode(spine[spine.length - 1], data); for (let i = spine.length - 2; i >= 0; i--) { const level = spine[i]; const lhsRaw = chainLhsRaw(level); (0, assert_1.guard)(lhsRaw !== undefined, 'a chain node always has a left operand'); const wrappedLhs = chainLhsArgument(level, data.completeAst.idMap); const lhsInfo = (0, process_argument_1.processFunctionArgument)(wrappedLhs, { ...data, precomputedValue: { nodeId: lhsRaw.info.id, info: result } }); result = processSingleChainNode(level, { ...data, precomputedFirstArg: { rootId: level.info.id, info: lhsInfo } }); } return result; } //# sourceMappingURL=process-named-call.js.map