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