UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

196 lines 8.61 kB
"use strict"; 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