UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

288 lines 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Environment = void 0; exports.isDefaultBuiltInEnvironment = isDefaultBuiltInEnvironment; exports.builtInEnvJsonReplacer = builtInEnvJsonReplacer; /** * Provides an environment structure similar to R. * This allows the dataflow to hold current definition locations for variables, based on the current scope. * @module */ const json_1 = require("../../util/json"); const identifier_1 = require("./identifier"); const assert_1 = require("../../util/assert"); const info_1 = require("../info"); const append_1 = require("./append"); const log_1 = require("../../util/log"); /** * Please use this function only if you do not know the object type. * Otherwise, rely on {@link IEnvironment#builtInEnv} */ function isDefaultBuiltInEnvironment(obj) { return typeof obj === 'object' && obj !== null && (obj.builtInEnv === true); } let environmentIdCounter = 1; // Zero is reserved for built-in environment /** @see REnvironmentInformation */ class Environment { id; /** Optional name for namespaced/non-anonymous environments, please only set if you know what you are doing */ n; /** if created by a closure, the node id of that closure */ c; parent; memory; cache; builtInEnv; constructor(parent, isBuiltInDefault = undefined) { this.id = isBuiltInDefault ? 0 : environmentIdCounter++; this.parent = parent; this.memory = new Map(); // do not store if not needed! if (isBuiltInDefault) { this.builtInEnv = isBuiltInDefault; } } /** please only use if you know what you are doing */ setClosureNodeId(nodeId) { this.c = nodeId; } /** * Provides the closure linked to this environment. * This is of importance if, for example, if you want to know the function definition associated with this environment. */ get closure() { return this.c; } /** * Create a deep clone of this environment. * @param recurseParents - Whether to also clone parent environments */ clone(recurseParents) { if (this.builtInEnv) { return this; // do not clone the built-in environment } const parent = recurseParents ? this.parent.clone(recurseParents) : this.parent; const clone = new Environment(parent, this.builtInEnv); clone.c = this.c; clone.n = this.n; clone.memory = new Map(this.memory.entries() .map(([k, v]) => [k, v.map(s => ({ ...s, cds: s.cds?.slice() })) ])); return clone; } /** * Define a new identifier definition within this environment. * @param definition - The definition to add. */ define(definition) { const [name, ns] = identifier_1.Identifier.toArray(definition.name); if (ns !== undefined && this.n !== ns) { return this.defineInNamespace(definition, ns); } const newEnvironment = this.clone(false); // When there are defined indices, merge the definitions if (definition.cds === undefined) { newEnvironment.memory.set(name, [definition]); } else { const existing = newEnvironment.memory.get(name); const inGraphDefinition = definition; if (existing !== undefined && inGraphDefinition.cds === undefined) { newEnvironment.memory.set(name, [inGraphDefinition]); } else if (existing === undefined || definition.cds === undefined) { newEnvironment.memory.set(name, [definition]); } else { existing.push(definition); } } return newEnvironment; } defineInNamespace(definition, ns) { if (this.n === ns) { return this.define(definition); } // navigate to parent until either before built-in or matching namespace const newEnvironment = this.clone(false); let current = newEnvironment; do { if (current.n === ns) { current.define(definition); return newEnvironment; } else if (current.parent && !current.parent.builtInEnv) { // clone parent current.parent = current.parent.clone(false); current = current.parent; } else { break; } } while (current.n !== ns); // we did not find the namespace, so we inject a new environment here log_1.log.warn(`Defining ${identifier_1.Identifier.getName(definition.name)} in namespace ${ns}, which did not exist yet in the environment chain => create (r should fail or we miss attachment).`); const env = new Environment(current.parent); env.n = ns; current.parent = env.define(definition); return newEnvironment; } defineSuper(definition) { const [name, ns] = identifier_1.Identifier.toArray(definition.name); const newEnvironment = this.clone(false); if (ns !== undefined && this.n !== ns) { newEnvironment.parent = newEnvironment.parent.defineInNamespace(definition, ns); return newEnvironment; } let current = newEnvironment; let last = undefined; let found = false; do { if (current.memory.has(name)) { current.memory.set(name, [definition]); found = true; break; } last = current; current.parent = current.parent.clone(false); current = current.parent; } while (!current.builtInEnv); if (!found) { (0, assert_1.guard)(last !== undefined, () => `Could not find global scope for ${name}`); last.memory.set(name, [definition]); } return newEnvironment; } /** * Assumes, that all definitions within other replace those within this environment (given the same name). * <b>But</b> if all definitions within other are maybe, then they are appended to the current definitions (updating them to be `maybe` from now on as well), similar to {@link appendEnvironment}. * This always recurses parents. */ overwrite(other, applyCds) { if (this.builtInEnv || this === other || !other || this.n !== other.n) { return this; } const map = new Map(this.memory); for (const [key, values] of other.memory) { const hasMaybe = applyCds === undefined ? values.length === 0 || values.some(v => v.cds !== undefined) : true; if (hasMaybe) { const old = map.get(key); if (!old && applyCds === undefined) { map.set(key, values); continue; } // we need to make a copy to avoid side effects for old reference in other environments const updated = old?.slice() ?? []; for (const v of values) { const { nodeId, definedAt } = v; const index = updated.find(o => o.nodeId === nodeId && o.definedAt === definedAt); if (index) { continue; } if (applyCds === undefined) { updated.push(v); } else { updated.push({ ...v, cds: v.cds ? applyCds.concat(v.cds) : applyCds.slice() }); } } map.set(key, updated); } else { map.set(key, values); } } const out = new Environment(this.parent.overwrite(other.parent, applyCds)); out.c = this.c; out.n = this.n; out.memory = map; return out; } /** * Adds all writes of `other` to this environment (i.e., the operations of `other` *might* happen). * This always recurses parents. */ append(other) { if (!other || this.builtInEnv || this === other || this.n !== other.n) { return this; } const map = new Map(this.memory); for (const [key, value] of other.memory) { const old = map.get(key); if (old) { map.set(key, (0, append_1.uniqueMergeValuesInDefinitions)(old, value)); } else { map.set(key, value); } } const out = new Environment(this.parent.append(other.parent)); out.memory = map; return out; } remove(id) { if (this.builtInEnv) { return this; } const [name, ns] = identifier_1.Identifier.toArray(id); if (ns !== undefined && this.n !== ns) { this.parent.remove(id); return this; } const definition = this.memory.get(name); let cont = true; if (definition !== undefined) { this.memory.delete(name); this.cache?.delete(name); cont = !definition.every(d => (0, info_1.happensInEveryBranch)(d.cds)); } if (cont) { this.parent.remove(name); } return this; } removeAll(names) { if (this.builtInEnv || names.length === 0) { return this; } const newEnv = this.clone(true); // we should optimize this later for (const { name } of names) { newEnv.remove(name); } return newEnv; } toJSON() { return this.builtInEnv ? { id: this.id, parent: this.parent, builtInEnv: this.builtInEnv, memory: this.memory, } : { id: this.id, parent: this.parent, memory: this.memory, }; } } exports.Environment = Environment; /** * Helps to serialize an environment, but replaces the built-in environment with a placeholder. */ function builtInEnvJsonReplacer(k, v) { if (isDefaultBuiltInEnvironment(v)) { return '<BuiltInEnvironment>'; } else { return (0, json_1.jsonReplacer)(k, v); } } //# sourceMappingURL=environment.js.map