Structure
Internally, environments are held in EffectRef cells and chained into a lexical scope tree:
- A toplevel environment has a
bindingsRefbut no parent. It is created byrunfrom the caller-supplied bindings object. - An inner environment has both a
bindingsRefand aparentpointer. Lambda calls create an inner environment that extends the enclosing scope with the parameter bindings.
getBinding walks the chain from innermost outward, returning Option.some(value) at the first match or Option.none() when no binding exists at any level.
String resolution
When the evaluator sees a string it callsevaluateReference:
getBinding returns Option.none(), Option.getOrElse(() => name) substitutes the original string. This means an unbound string is not an error — it evaluates to itself. That makes bare string literals safe inside object values or quote expressions without any extra quoting.
The stdlib default environment
stdlib is a pre-built environment that ships with @lionlang/core. Every entry is namespaced as <module>/<name>:
stdlib directly to run gives access to all built-in modules without any additional setup.
Extending the environment
Spreadstdlib and add your own keys to inject custom values, host objects, or plain functions:
"price" is evaluated as a string it resolves to 100. When "clamp" is evaluated it resolves to the JavaScript function, which is then called by the function-call handler with the evaluated arguments.
Any JavaScript value — plain objects, class instances, native functions, or even other Effect computations — can live in the environment. Lion makes no assumptions about the shape of bound values beyond the fact that callable values are invoked as Lion functions when they appear as the head of an array expression.
Global mutation with define
The define special form writes a new binding into the toplevel environment, regardless of how deeply nested the current call is:
run call (or in a begin block) can reference "answer" and receive 42. Lambdas created after the define also observe the new binding because they look up names at call time through the environment chain.
Lexical scope in lambdas
Whenlambda is evaluated it captures the current LionEnvironmentService value as enclosedEnvironment. Each call then creates a new inner environment extending that captured scope:
define mutations made after creation (because the global Ref is shared through the chain).