x-engine-c v0.2.31
The C engine for x-lang
Loading...
Searching...
No Matches
x-env.h File Reference

Environments – first-class values the evaluator binds and looks up in. More...

#include "x-obj.h"

Go to the source code of this file.

Macros

The pair
#define x_env_bindings(E)   x_firstobj(E)
 
#define x_env_parent(E)   x_restobj(E)
 
#define x_env_isroot(B, E)   x_obj_isnil((B), x_restobj(E))
 
#define x_env_istree(B, E)   (x_env_isroot((B), (E)) || ! x_obj_type_isspair(E))
 

Functions

x_obj_t * x_env_make (x_obj_t *p_base, x_obj_t *p_parent)
 
x_obj_t * x_env_lookup (x_obj_t *p_base, x_obj_t *p_args)
 
x_obj_t * x_env_bind (x_obj_t *p_base, x_obj_t *p_args)
 
x_obj_t * x_env_extend (x_obj_t *p_base, x_obj_t *p_args)
 

Detailed Description

Environments – first-class values the evaluator binds and looks up in.

An environment is one pair, (bindings . parent). The root's parent is nil and its bindings are a tree (x-alist.c's BST, for the size of a loaded library); every other environment's bindings are an alist of (name . value) cells and its parent is the environment it was made in. A procedure call makes a child of the closure's environment; an operative body runs in a child of its static environment and receives the caller's environment as a value; def binds in the current environment; eval with an environment makes that one current. That is the whole of scope, and these four operations are all of it.

Author
Jon Ruttan (jonru.nosp@m.ttan.nosp@m.@gmai.nosp@m.l.co.nosp@m.m)

Macro Definition Documentation

◆ x_env_bindings

#define x_env_bindings (   E)    x_firstobj(E)

The alist, or the root's tree.

◆ x_env_isroot

#define x_env_isroot (   B,
  E 
)    x_obj_isnil((B), x_restobj(E))

A root has no parent.

◆ x_env_istree

#define x_env_istree (   B,
  E 
)    (x_env_isroot((B), (E)) || ! x_obj_type_isspair(E))

Keeps its bindings as a tree: a root, and an environment whose spine the language made with pair – a list pair, where the engine makes its own frames of structural pairs. A module's scope is one: its hundreds of names would otherwise be an alist every root lookup from inside walks.

◆ x_env_parent

#define x_env_parent (   E)    x_restobj(E)

The enclosing environment, nil at the root.

Function Documentation

◆ x_env_bind()

x_obj_t * x_env_bind ( x_obj_t *  p_base,
x_obj_t *  p_args 
)

Bind a symbol to a value in an environment itself: an existing binding there is updated in place, otherwise one is added. Never touches a parent. Returns the value. Argument run: (env, symbol, value).

Bind p_sym to p_val in p_env itself.

A binding the environment already holds is updated in place; otherwise one is added – to the tree of a root or a module's scope (x_env_istree), or in front of a frame's alist. A parent's binding of the same name is never touched: it is shadowed, which is what a definition in a child means. def is this on the current environment; the C binding doors and base bind are this on a root.

Parameters
p_basex_obj_t* – Base (execution context)
p_argsx_obj_t* – Argument run: (env, symbol, value) – the environment to bind in, the symbol, the value
Returns
x_obj_t* – The value
Note
The tree insert mutates in place (x_alist_bst_insert), so every closure whose chain reaches this root sees the new binding at its next lookup – a top-level definition made after a closure was created is visible to it, as it must be.

◆ x_env_extend()

x_obj_t * x_env_extend ( x_obj_t *  p_base,
x_obj_t *  p_args 
)

Make a child of an environment with parameters bound to values: the environment a procedure body or an operative body runs in. Argument vector: (parent, params, values).

Make a child environment with parameters bound to values.

The environment a procedure body or an operative body runs in: a fresh (bindings . parent) pair whose parent is p_parent, the closure's or the operative's static environment, and whose bindings are the parameters. Handles three cases: (1) variadic – a bare symbol binds to the entire remaining value list, (2) base – no more params, (3) one parameter to one value, then the rest.

Parameters
p_basex_obj_t* – Base (execution context)
p_argsx_obj_t* – Argument run: (parent, params, values) – the environment the new one is a child of, the parameter list (or a single symbol for variadic), and the value list
Returns
x_obj_t* – The new environment

The parent is never modified. The bindings are new cells in the new environment; p_parent is only pointed at. Fewer values than parameters binds the remainder to nil, symmetric with surplus values, which are ignored once the parameters run out.

Note
The variadic case (bare symbol for p_params) binds the ENTIRE remaining value list, not just one value. This implements rest-parameter semantics: (fn (a . rest) ...).
See also
x_env_bind – def, the same binder one name at a time
x_eval_body_tco – saves/restores env around a body

◆ x_env_lookup()

x_obj_t * x_env_lookup ( x_obj_t *  p_base,
x_obj_t *  p_args 
)

The (name . value) cell binding a symbol in an environment or an ancestor, or NULL when no environment on the chain binds it. Names are found by identity; a symbol interned in another base stands for this base's own symbol of its spelling. Argument run: (env, symbol).

The cell binding p_sym in p_env or an ancestor.

Walks from p_env to the root: a frame searches its alist of (name . value) cells by symbol identity; the root, and a module's scope (x_env_istree), search their tree, also by identity. The first hit wins, so a child's binding shadows a parent's. This is the whole of symbol lookup – x_type_symbol_eval and set! call nothing else.

Symbols intern per base, and a name is found by identity, not by spelling: a base's own symbol finds only what was bound under it, so a name the host bound into a child under the host's symbol is not found by the child's symbol of the same spelling. A FOREIGN symbol – one interned in another base, as every symbol of a form the host read and evaluates in a child is – has no identity here, so it stands for this base's own symbol of its spelling, and that is what is looked up. That is what lets (base eval B (lit (+ 2 3))) hand a child the host's + and reach the child's binding of its own +. The retry runs only when the identity lookup at the root missed.

Under X_PROFILE, each binding compared in an environment with a parent – a frame, or a module's environment – counts one in the base's profile-env-steps cell. The root's tree counts its own lookups, in profile-bst-hits and profile-bst-misses, so the walk to the root is what this one cell adds.

Parameters
p_basex_obj_t* – Base (execution context)
p_argsx_obj_t* – Argument run: (env, symbol) – the environment to start from, and the symbol
Returns
x_obj_t* – The (name . value) cell, or NULL when unbound

◆ x_env_make()

x_obj_t * x_env_make ( x_obj_t *  p_base,
x_obj_t *  p_parent 
)

Make an empty environment whose parent is p_parent (nil for a root).

Make an empty environment whose parent is p_parent.

An environment is one pair, (bindings . parent). A nil parent makes a root, whose bindings are kept as a tree; any other environment keeps an alist. x_eval_make builds the base's root this way; procedure and operative calls make children through x_env_extend; guard makes one for its error variable.

Parameters
p_basex_obj_t* – Base (execution context)
p_parentx_obj_t* – The enclosing environment, or nil for a root
Returns
x_obj_t* – The new, empty environment