@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
282 lines • 11.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.S7SyntheticFunArgSuffix = void 0;
exports.processS7NewGeneric = processS7NewGeneric;
exports.processMakeConstructor = processMakeConstructor;
const known_call_handling_1 = require("../known-call-handling");
const r_function_call_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
const built_in_apply_1 = require("./built-in-apply");
const logger_1 = require("../../../../../logger");
const linker_1 = require("../../../../linker");
const common_1 = require("../common");
const unpack_argument_1 = require("../argument/unpack-argument");
const r_argument_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-argument");
const edge_1 = require("../../../../../graph/edge");
const type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type");
const assert_1 = require("../../../../../../util/assert");
const identifier_1 = require("../../../../../environments/identifier");
const node_value_1 = require("../../../../../eval/resolve/node-value");
const r_value_1 = require("../../../../../eval/values/r-value");
const vertex_1 = require("../../../../../graph/vertex");
const range_1 = require("../../../../../../util/range");
const built_in_proc_name_1 = require("../../../../../environments/built-in-proc-name");
/**
* Process an S7 new generic dispatch call like `new_generic` or `setGeneric`.
*/
function processS7NewGeneric(name, args, rootId, data, config) {
if (args.length < 1) {
logger_1.dataflowLogger.warn('empty s7 new_generic, skipping');
return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, origin: 'default' }).information;
}
const params = {
[config.args.name]: 'name',
};
if (config.args.dispatchArg) {
params[config.args.dispatchArg] = 'dispatchArg';
}
params[config.args.fun] = 'fun';
params['...'] = '...';
const argMaps = (0, linker_1.pMatch)((0, common_1.convertFnArguments)(args), params);
const genName = (0, unpack_argument_1.unpackArg)(r_argument_1.RArgument.getWithId(args, argMaps.get('name')?.[0]));
if (!genName) {
return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, origin: 'default' }).information;
}
const n = node_value_1.NodeValue.of(genName.info.id, data);
const accessedIdentifiers = [];
if (n.type === 'set') {
for (const elem of n.elements) {
if (elem.type === 'string' && (0, r_value_1.isValue)(elem.value)) {
accessedIdentifiers.push(elem.value.str);
}
}
}
if (accessedIdentifiers.length === 0) {
logger_1.dataflowLogger.warn('s7 new_generic non-resolvable skipping');
return (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, origin: 'default' }).information;
}
data = { ...data, currentS7name: accessedIdentifiers };
let funArg = (0, unpack_argument_1.unpackArg)(r_argument_1.RArgument.getWithId(args, argMaps.get('fun')?.[0]))?.info.id;
const effectiveArgs = args.slice();
if (!funArg) {
const dispatchArg = (0, unpack_argument_1.unpackArg)(r_argument_1.RArgument.getWithId(args, argMaps.get('dispatchArg')?.[0]));
const newFun = makeS7DispatchFDef(name, [dispatchArg?.lexeme ?? undefined], rootId, args.length, data.completeAst.idMap);
// fake it 'function([dispatch_args],...) S7_dispatch()'
effectiveArgs.push(newFun[0]);
funArg = newFun[1];
}
const info = (0, known_call_handling_1.processKnownFunctionCall)({ name, forceArgs: 'all', args: effectiveArgs, rootId, data, origin: built_in_proc_name_1.BuiltInProcName.S7NewGeneric }).information;
info.graph.addEdge(rootId, funArg, edge_1.EdgeType.Returns);
info.entryPoint = funArg;
const fArg = info.graph.getVertex(funArg);
if (vertex_1.FunctionDefinitionVertex.is(fArg)) {
fArg.mode ??= ['s4', 's7'];
}
return info;
}
/**
* Process a call that **returns a function**: S7/S4 constructor factories (`make_constructor`, `new_class`,
* `setClass`) and generic function factories (`Negate`, `Vectorize`, `partial`, …). We model the result as a
* synthetic function definition so the assigned symbol is recognized as a **function** rather than a plain
* constant.
*/
function processMakeConstructor(name, args, rootId, data, config) {
// synthesise `function(...) S7_dispatch()` and make the call return it
const [funArg, funId] = makeS7DispatchFDef(name, [], rootId, args.length, data.completeAst.idMap);
const info = (0, known_call_handling_1.processKnownFunctionCall)({ name, forceArgs: 'all', args: [...args, funArg], rootId, data, origin: built_in_proc_name_1.BuiltInProcName.S7MakeConstructor }).information;
info.graph.addEdge(rootId, funId, edge_1.EdgeType.Returns);
info.entryPoint = funId;
const fArg = info.graph.getVertex(funId);
if (vertex_1.FunctionDefinitionVertex.is(fArg) && config?.mode) {
fArg.mode ??= config.mode.slice(); // copy: mode is mutated in place later, config.mode is shared
}
if (config?.wrapIndex !== undefined) {
linkWrappedFunction(info, args, config.wrapIndex, config.wrapName, data);
}
return info;
}
/** Mark the wrapped function of an eager higher-order wrapper (`Negate`/`Vectorize`/`partial`) as called. */
function linkWrappedFunction(info, args, wrapIndex, wrapName, data) {
let wrapped = undefined;
if (wrapName !== undefined) {
wrapped = args.find(a => a !== r_function_call_1.EmptyArgument && a.name?.content === wrapName);
}
if (wrapped === undefined) {
let pos = 0;
for (const a of args) {
if (a === r_function_call_1.EmptyArgument || a.name) {
continue;
}
if (pos === wrapIndex) {
wrapped = a;
break;
}
pos++;
}
}
if (wrapped === undefined || wrapped === r_function_call_1.EmptyArgument || !wrapped.value) {
return;
}
const resolved = (0, built_in_apply_1.resolveFunctionArgument)(wrapped.value, data, {});
if (resolved === undefined || resolved.anonymous) {
return;
}
const vertex = info.graph.getVertex(resolved.functionId);
if (!vertex_1.UseVertex.is(vertex)) {
return;
}
info.graph.updateToFunctionCall({
tag: vertex_1.VertexType.FunctionCall,
id: resolved.functionId,
name: resolved.functionName,
args: [],
environment: data.environment,
onlyBuiltin: false,
cds: data.cds,
origin: [built_in_proc_name_1.BuiltInProcName.Function]
});
}
/**
* Node-id suffix of the `fun = function(...) S7_dispatch()` argument flowR synthesizes for S7 calls that do not
* carry one (`new_class`, `new_generic`, ...). It stands for no argument in the source, so consumers that reason
* about how the *code* calls a function (e.g. matching a call against a package's signature history) must skip it.
*/
exports.S7SyntheticFunArgSuffix = '-s7-new-generic-fun-arg';
// 'function([dispatch_args],...) S7_dispatch()'; returns the value id
function makeS7DispatchFDef(name, names, rootId, args, idMap) {
const argNameId = rootId + '-s7-new-generic-fun-arg-name';
const r = name.location ?? range_1.SourceRange.invalid();
const argName = {
type: type_1.RType.Symbol,
lexeme: 'fun',
content: 'fun',
info: {
id: argNameId,
nest: name.info.nest,
role: "arg-n" /* RoleInParent.ArgumentName */,
fullRange: r,
adToks: undefined,
file: name.info.file,
parent: rootId,
index: 0
},
location: r,
};
idMap.set(argNameId, argName);
const funcNameId = rootId + '-s7-new-generic-fun-name';
const funcName = {
type: type_1.RType.Symbol,
lexeme: 'S7_dispatch',
info: {
id: funcNameId,
nest: name.info.nest,
role: "call-name" /* RoleInParent.FunctionCallName */,
fullRange: r,
adToks: undefined,
file: name.info.file,
parent: rootId,
index: 0
},
location: r,
content: identifier_1.Identifier.make('S7_dispatch', 'S7'),
};
const funcBody = {
type: type_1.RType.FunctionCall,
location: r,
lexeme: 'S7_dispatch',
named: true,
functionName: funcName,
arguments: [],
info: {
id: rootId + '-s7-new-generic-fun-body',
nest: name.info.nest,
role: "fun-b" /* RoleInParent.FunctionDefinitionBody */,
fullRange: r,
adToks: undefined,
file: name.info.file,
parent: rootId,
index: 0
}
};
const fdefId = rootId + '-s7-new-generic-fun-fdef';
const argValue = {
type: type_1.RType.FunctionDefinition,
info: {
file: name.info.file,
id: fdefId,
nest: name.info.nest,
role: "arg-v" /* RoleInParent.ArgumentValue */,
parent: rootId,
index: args + 1,
adToks: undefined,
fullRange: r,
},
lexeme: 'function',
location: r,
parameters: [...names.filter(assert_1.isNotUndefined), '...'].map((n, i) => {
const paramId = fdefId + `-param-${i}`;
const paramNameId = paramId + '-name';
const paramName = {
type: type_1.RType.Symbol,
lexeme: n,
content: n,
info: {
id: paramNameId,
nest: name.info.nest,
role: "param-n" /* RoleInParent.ParameterName */,
fullRange: r,
adToks: undefined,
file: name.info.file,
index: i,
parent: paramId
},
location: r,
};
const param = {
type: type_1.RType.Parameter,
location: r,
lexeme: n,
name: paramName,
defaultValue: undefined,
special: n === '...',
info: {
id: paramId,
nest: name.info.nest,
role: "param" /* RoleInParent.FunctionDefinitionParameter */,
parent: fdefId,
index: i,
adToks: undefined,
file: name.info.file,
fullRange: r,
}
};
idMap.set(paramNameId, paramName);
idMap.set(paramId, param);
return param;
}),
body: funcBody,
};
idMap.set(funcNameId, funcName);
idMap.set(funcBody.info.id, funcBody);
idMap.set(fdefId, argValue);
const argId = rootId + exports.S7SyntheticFunArgSuffix;
const argument = {
type: type_1.RType.Argument,
lexeme: 'fun',
location: r,
info: {
id: argId,
nest: name.info.nest,
role: "call-arg" /* RoleInParent.FunctionCallArgument */,
fullRange: r,
adToks: undefined,
file: name.info.file,
parent: rootId,
index: args + 1
},
name: argName,
value: argValue
};
idMap.set(argument.info.id, argument);
return [argument, argValue.info.id];
}
//# sourceMappingURL=built-in-s-seven-new-generic.js.map