@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
113 lines • 5.61 kB
JavaScript
;
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