Semantics & Scoping

Grammar decides what a program looks like; semantics decides what it means. Names must be bound to values, scopes must resolve them, and evaluation must give every phrase one deterministic reading — the part of a DSL users feel every single run.

Bindings & Environments

A binding attaches a name to a value or storage slot; the environment is the chain of scopes that answers “what does this name mean here?”.

Names to Storage

Write a DSL spec in terms of bindings, not variables: each name occurrence resolves to a binding, and evaluating that name yields the bound value (or storage address). Getting this layer right makes scoping, typing, and later compilers fall into place.

Environment Chains

Nested blocks create nested environments; resolution walks outward. The order of that walk is the scoping rule — and it is specified, not chosen by the implementation.

Scoping Rules

Lexical scope resolves by text nesting; dynamic scope resolves by call stack, which shadows differently

Figure 1 — The same text means different things under each rule; a spec must pick one.

Lexical (Static) Scope

Resolution follows the text of the program: a name means whatever an enclosing block declared, regardless of who calls the function. Predictable, toolable (editors and AI assistants can follow it), and the modern default.

Dynamic Scope

Resolution follows the call stack at runtime: a function reads the binding of its caller. Historically AutoLISP and some shells behave this way; it is convenient for debugging magic but surprises readers, which is why new languages avoid it.

Which to Specify

For a DSL: lexical scope, always, unless the domain genuinely needs stack-like context. Then write “lexical scope with these block rules” into the one-page design document before Phase 2.

Evaluation

Semantics is incomplete without saying when and in what order things happen.

Strict vs. Lazy

Strict (eager) evaluation computes arguments before calling; lazy evaluation defers until needed (Haskell). DSLs and configuration grammars are overwhelmingly strict — laziness pays only in streaming or infinite-data domains.

Order of Evaluation

Specify left-to-right for operands and short-circuit rules for and/or; leave nothing to the implementation. Also declare what happens on the first runtime failure: stop, or continue and report all errors (batch DSLs often prefer the latter).

Name Resolution as a Service

Resolution Rules

Write them as an ordered list: innermost block first, then enclosing blocks, then the module/global scope, then built-ins, then an explicit error. Shadowing (a block redeclaring a name) is a feature — document that the inner binding hides the outer one until the block ends.

Resolution Errors

Undefined-name at runtime is the worst kind for a DSL because it appears late. When feasible, resolve names before execution (Phase 2 analysis) so the error is a compile-time diagnostic instead.

Example: Scope in Action

Two tiny programs, one difference: where box resolves.

Lexical (Python)

# scope.py — lexical scope: text nesting wins
box = "global"                    # outer binding
def inside():
    print(box)                    # resolves to the enclosing block: "global"
inside()                          # reads the def-site text, not the caller

Dynamic (AutoLISP note)

; scope.lsp — dynamic scope: the caller's binding wins
(setq box "global")
(defun inside () (princ box))     ; box resolved against the call stack
(defun caller () (setq box "caller") (inside))
(caller)                          ; prints "caller", not "global"

Both are valid specifications — but only one of them is predictable for your users. Pick lexical scope unless the domain demands stack context, and say so in the design document.

Next Steps

Continue Phase 1

Meaning needs a vocabulary: Types & Values classifies that vocabulary, and Errors & Diagnostics decides how failed programs speak. Then Phase 2 turns meaning into execution.

Resources