Skip to content

env: Env

Eugene Lazutkin edited this page Jan 11, 2022 · 5 revisions

This class is used to create environment objects. An environment allows binding variables to values or other variables, check if a variable is bound (only unbound variables can be bound), and rollback bindings in a generational fashion. This functionality is very important for a solver.

It is defined in env and can be accessed like that:

import {Env} from 'deep6/env.js';

Introduction

const env = new Env();

env.isBound('a'); // false
env.isBound('b'); // false
env.isBound('c'); // false

// push a generation frame
env.push();

env.bindVal('a', 1);

env.isBound('a'); // true
env.isBound('b'); // false
env.isBound('c'); // false

env.get('a');     // 1

// push a generation frame
env.push();

env.isBound('a'); // true
env.isBound('b'); // false
env.isBound('c'); // false

env.bindVar('c', 'b');

env.isBound('a'); // true
env.isBound('b'); // false
env.isBound('c'); // false

env.isAlias('a', 'b'); // false
env.isAlias('b', 'c'); // true
env.isAlias('c', 'a'); // false

env.bindVal('b', 2);

env.isBound('a'); // true
env.isBound('b'); // true
env.isBound('c'); // true

env.get('a');     // 1
env.get('b');     // 2
env.get('c');     // 2

// drop a generation frame
env.pop();

env.isBound('a'); // true
env.isBound('b'); // false
env.isBound('c'); // false

Class: Env

This class defines objects with the following properties:

  • depth — an integer value of the depth, which is the current generation number. The initial value is 0.
  • variables — an object with null prototype, which is used to keep track of variables, which are defined by strings or symbols. Initially, this dictionary is empty.
  • values — an object with null prototype, which is used to keep track of values corresponding to variables. Any type of value can be used. Initially, this dictionary is empty.

Usually variables and values are not accessed directly. depth can be used with the revert(depth) method described below.

The class defines the following methods:

constructor()

The constructor takes no arguments. It initializes an empty environment object as described above.

push()

The method advances depth by 1 and returns nothing. Computationally it is very cheap (O(1)). Usually, it is used with pop() and revert(depth) described below.

pop()

The method reduces depth by 1 and returns nothing. While reducing the depth, it will undo all bindings that happened since the previous push(). Computationally it is inexpensive.

It will throw an error if depth becomes negative.

Example:

env.push(); // creates new frame

// multiple bindings of variables and values

env.pop();
// now we returned to the previous state
// and can try other ways to bind variables

revert(depth)

The method reverts to the previous depth and returns nothing. While going back to the requested depth, it will undo all bindings that happened since that time. It takes the following arguments:

  • depth — an integer value of the previous depth.
    • It will throw an error if the current depth is lower than the requested depth.

Example:

const depth = env.depth; // remember the current depth

// multiple bindings of variables and values
// possibly with pushing to a higher depth

env.revert(depth);
// now we returned to the previous state
// and can try other ways to bind variables

bindVar(name1, name2)

The method declares two variables to be aliases of each other and returns nothing. Binding a value to one variable will automatically bind all its aliases. It takes the following arguments:

  • name1 — a variable name as a string or a symbol.
  • name2 — a variable name as a string or a symbol.

Both names and all their existing aliases will be aliased. The procedure is completely symmetric and it doesn't matter in what order they are aliased.

No checks are done that variable names are not the same nor aliased already. No checks are done to ensure that they are not bound to different values. Correctness checks should be done externally before calling the method.

bindVal(name, value)

The method binds a variable (and all its aliases created with bindVar()) to a value. It takes the following arguments:

  • name — a variable name as a string or a symbol.
  • value — an arbitrary value.

No checks are done that the variable is unbound, or bound to the same value. Correctness checks should be done externally before calling the method.

isBound(name)

The method returns a truthy value if a variable is already bound to a value. Computationally it is very cheap (O(1)). It takes the following arguments:

  • name — a variable name as a string or a symbol.

The current implementation:

isBound(name) {
  return name in this.values;
}

isAlias(name1, name2)

The method returns a truthy value if variables are already aliased to each other. Computationally it is very cheap (O(1)). It takes the following arguments:

  • name1 — a variable name as a string or a symbol.
  • name2 — a variable name as a string or a symbol.

The procedure is completely symmetric and it doesn't matter in what order they are aliased.

The current implementation:

isAlias(name1, name2) {
  const u = this.variables[name2];
  return u && u[name1] === 1;
}

get(name)

The method returns a value bound to a variable. Computationally it is very cheap (O(1)). It takes the following arguments:

  • name — a variable name as a string or a symbol.

No checks are done that the variable is bound. Correctness checks should be done externally before calling the method. If a variable is not bound in the environment, get() will return undefined.

Example:

const value = 1; // some arbitrary value
env.bindVal('a', value);
env.get('a') === value; // true

The current implementation:

get(name) {
  return this.values[name];
}

getAllValues()

This is a debugging method. It returns an array of objects with the following properties:

  • name — a variable name as a string or a symbol.
  • value — an arbitrary value.

Only currently bound variables are listed. All aliases will be included.