@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
196 lines • 8.61 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.bindArgs = bindArgs;
exports.resolveArgToEnvir = resolveArgToEnvir;
exports.resolveEnvirArg = resolveEnvirArg;
exports.resolveSymbolToEnvir = resolveSymbolToEnvir;
exports.routeWrittenToCustomEnv = routeWrittenToCustomEnv;
const r_function_call_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
const type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type");
const unpack_argument_1 = require("../argument/unpack-argument");
const resolve_by_name_1 = require("../../../../../environments/resolve-by-name");
const identifier_1 = require("../../../../../environments/identifier");
const define_1 = require("../../../../../environments/define");
const prefix_1 = require("../../../../../../util/prefix");
/**
* Core resolver: maps a list of identifier definitions (from {@link resolveByName}) to an
* {@link EnvirResolution}. Accepts `undefined` so callers can pass `resolveByName` results
* directly without an intermediate null check.
*
* Multiple reaching definitions (e.g. from if/else branches) are accepted only when every
* definition carries an envState; their envStates are merged into a single combined snapshot.
*/
function resolveDefsToEnvirResolution(defs, nodeId, data) {
if (!defs || defs.length === 0) {
return undefined;
}
const inDefs = defs;
if (inDefs.length === 1) {
const envState = inDefs[0].envState;
if (!envState) {
return undefined;
}
const envDef = inDefs[0];
return { envirData: { ...data, environment: envState }, envDef, envirNodeId: nodeId };
}
if (!inDefs.every(d => d.envState !== undefined)) {
return undefined;
}
let mergedEnvState = inDefs[0].envState;
for (let i = 1; i < inDefs.length; i++) {
for (const [, varDefs] of inDefs[i].envState.current.memory) {
for (const varDef of varDefs) {
const named = varDef;
if (named.name !== undefined) {
mergedEnvState = (0, define_1.define)(named, false, mergedEnvState);
}
}
}
}
const envDef = {
...inDefs[0],
envState: mergedEnvState
};
return { envirData: { ...data, environment: mergedEnvState }, envDef, envirNodeId: nodeId };
}
/**
* Binds call arguments to formal parameter names following R's standard matching rules:
* 1. Exact name matches (named args bound to exact-matching formal params).
* 2. Partial (pmatch) name matches via {@link findByPrefixIfUnique}.
* 3. Remaining unnamed args fill remaining unbound formal params left-to-right.
*
* Pass `paramNames` as the full formal parameter list (excluding `...`) so ambiguous
* prefixes are rejected correctly. Use this when multiple formals must be found
* simultaneously so that named-arg binding is consistent across all formals.
*/
function bindArgs(args, paramNames) {
const bound = new Map();
const used = new Set();
/* pass 1: exact name matches */
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === r_function_call_1.EmptyArgument || arg.name === undefined) {
continue;
}
const n = arg.name.content;
if (paramNames.includes(n) && !bound.has(n)) {
bound.set(n, arg);
used.add(i);
}
}
/* pass 2: partial (pmatch) name matches */
for (let i = 0; i < args.length; i++) {
if (used.has(i)) {
continue;
}
const arg = args[i];
if (arg === r_function_call_1.EmptyArgument || arg.name === undefined) {
continue;
}
const matched = (0, prefix_1.findByPrefixIfUnique)(arg.name.content, paramNames);
if (matched !== undefined && !bound.has(matched)) {
bound.set(matched, arg);
used.add(i);
}
}
/* pass 3: remaining unnamed args fill remaining formal params left-to-right */
let formalIdx = 0;
for (let i = 0; i < args.length; i++) {
if (used.has(i)) {
continue;
}
const arg = args[i];
if (arg === r_function_call_1.EmptyArgument || arg.name !== undefined) {
continue;
}
while (formalIdx < paramNames.length && bound.has(paramNames[formalIdx])) {
formalIdx++;
}
if (formalIdx < paramNames.length) {
bound.set(paramNames[formalIdx], arg);
used.add(i);
formalIdx++;
}
}
return bound;
}
/**
* Resolves a single already-found argument (e.g. from {@link bindArgs}) to an
* {@link EnvirResolution} when the argument is a symbol that holds a tracked envState.
* Returns `undefined` when the arg is empty, non-symbolic, or unresolved.
*/
function resolveArgToEnvir(arg, data) {
if (arg === r_function_call_1.EmptyArgument) {
return undefined;
}
const node = (0, unpack_argument_1.unpackArg)(arg);
if (node?.type !== type_1.RType.Symbol) {
return undefined;
}
return resolveDefsToEnvirResolution((0, resolve_by_name_1.resolveByName)(node.content, data.environment, identifier_1.ReferenceType.Variable), node.info.id, data);
}
/**
* Scans `args` for an argument named `argName` (default `'envir'`), or - when
* `positionalFallbackIndex` is given - for the arg at that positional index when
* no named match is found. When the resolved argument is a symbol that resolves
* to a variable with a tracked {@link InGraphIdentifierDefinition#envState},
* returns the resolved context; otherwise returns `undefined`.
*
* Named matching uses pmatch semantics: pass `allParamNames` (the full formal parameter
* list) so ambiguous prefixes are rejected. Defaults to `[argName]`, which allows
* prefix matches for `argName` only.
*
* When multiple formals must be matched simultaneously (e.g. `data` and `expr` in `with`),
* use {@link bindArgs} + {@link resolveArgToEnvir} instead so named binding is consistent.
*/
function resolveEnvirArg(args, data, argName = 'envir', positionalFallbackIndex, allParamNames = [argName]) {
/* named pass: pmatch against the full parameter list */
for (const arg of args) {
if (arg === r_function_call_1.EmptyArgument || arg.name === undefined) {
continue;
}
if ((0, prefix_1.findByPrefixIfUnique)(arg.name.content, allParamNames) === argName) {
return resolveArgToEnvir(arg, data);
}
}
/* positional fallback (only when no named match existed) */
if (positionalFallbackIndex !== undefined) {
let pos = 0;
for (const arg of args) {
if (arg === r_function_call_1.EmptyArgument || arg.name !== undefined) {
continue;
}
if (pos === positionalFallbackIndex) {
return resolveArgToEnvir(arg, data);
}
pos++;
}
}
return undefined;
}
/**
* Resolves a symbol by name to an {@link EnvirResolution} when the symbol holds a tracked
* environment. Handles multiple reaching definitions (e.g. from if/else branches) by
* merging their envStates - see {@link resolveDefsToEnvirResolution}.
* Returns `undefined` when the name cannot be resolved or none of its definitions carry an envState.
*/
function resolveSymbolToEnvir(symbolName, nodeId, data) {
return resolveDefsToEnvirResolution((0, resolve_by_name_1.resolveByName)(symbolName, data.environment, identifier_1.ReferenceType.Variable), nodeId, data);
}
/**
* After processing an expression that writes into a custom environment, moves the
* written definitions from the caller's scope into `envDef`'s tracked `envState`
* and re-defines the holder variable in the returned environment.
*/
function routeWrittenToCustomEnv(result, envDef, newDefAt, definedAt) {
const written = result.out.filter((d) => d.name !== undefined && 'definedAt' in d &&
(definedAt === undefined || d.definedAt === definedAt));
let newEnvState = envDef.envState;
const namesToRemove = written.map(w => ({ name: w.name }));
for (const w of written) {
newEnvState = (0, define_1.define)(w, false, newEnvState);
}
const newEnvironment = (0, define_1.define)({ ...envDef, definedAt: newDefAt, envState: newEnvState }, false, { current: result.environment.current.removeAll(namesToRemove), level: result.environment.level });
return { ...result, environment: newEnvironment };
}
//# sourceMappingURL=built-in-envir-utils.js.map