On this page:
2.1.1 Binding Spaces
2.1.2 Binding Phases

2.1 Identifiers, Binding, and Scopes🔗ℹ

An identifier is a source-program entity. Parsing a Rhombus program reveals that some identifiers correspond to variables, some refer to primitive syntactic forms like fun, some refer to derived syntac forms as implemented by macros, and some are quoted to produce symbols or syntax objects. An identifier binds another (i.e., it is a binding) when the former is parsed as a variable or syntactic form and the latter is parsed as a reference to the former; the latter is bound.

For example, as a fragment of source, the text

def x = 5

x

includes two identifiers: def and x (which appears twice). When this source is parsed in a context where def has its usual meaning, the first x binds the second x.

Bindings and references are determined through scope sets. A scope corresponds to a region of the program that is either in part of the source or synthesized through elaboration of the source. Nested binding contexts (such as nested functions) create nested scopes, while macro expansion creates scopes that overlap in more complex ways. Conceptually, each scope is represented by a unique token, but the token is not directly accessible. Instead, each scope is represented by a value that is internal to the representation of a program.

A form is a fragment of a program, such as an identifier or a function call. A form is represented as a syntax object, and each syntax object has an associated set of scopes (i.e., a scope set). In the above example, the representations of the xs include the scope that corresponds to the def form.

When a form parses as the binding of a particular identifier, parsing updates a global table that maps a combination of an identifier’s symbol and scope set to its meaning: a variable or a macro. An identifier refers to a particular binding when the reference’s symbol and the identifier’s symbol are the same, and when the reference’s scope set is a superset of the binding’s scope set. For a given identifier, multiple bindings may have scope sets that are subsets of the identifier’s; in that case, the identifier refers to the binding whose set is a superset of all others; if no such binding exists, the reference is ambiguous (and triggers a syntax error if it is parsed as an expression). A binding shadows any binding (i.e., it is shadowing any binding) with the same symbol but a subset of scopes.

For example, in

fun (x):

  x

in a context where fun corresponds to the usual syntactic form, the parsing of fun introduces a new scope for the binding of x. Since the second x receives that scope as part of the fun body, the first x binds the second x. In the more complex case

fun (x):

  fun (x):

    x

the inner run creates a second scope for the second x, so its scope set is a superset of the first x’s scope set—which means that the binding for the second x shadows the one for the first x, and the third x refers to the binding created by the second one.

A top-level binding is a binding from a definition at the top-level; a module binding is a binding from a definition in a module; all other bindings are local bindings. Within a module, references to top-level bindings are disallowed. An identifier without a binding is unbound.

Throughout the documentation, identifiers are typeset to suggest the way that they are parsed. A hyperlinked identifier like fun indicates a reference to a syntactic form or variable. A plain identifier like x is a variable or a reference to an unspecified top-level variable.

2.1.1 Binding Spaces🔗ℹ

A binding space, or just space, represents a distinct syntactic category that has its own set of bindings. A binding space is implemented by a specific scope for the space; an identifier is bound in a space if its binding includes the space’s scope in its scope set. As a special case, the expression space has no correspond scope; bindings in that space correspond to the absence of other space’s scopes.

Binding forms bind identifiers in specific spaces. The def, let, and fun forms, for example, bind in the expression space. They may also bind in the static-information space to record static information about the binding.

The import and export forms include support for bindings spaces through subforms like only_space and except_space.

2.1.2 Binding Phases🔗ℹ

Every binding has a phase level in which it can be referenced, where a phase level normally corresponds to an integer (but the special label phase level does not correspond to an integer). Phase level 0 corresponds to the run time of the enclosing module (or the run time of top-level expressions). Bindings in phase level 0 constitute the base environment. Phase level 1 corresponds to the time during which the enclosing module (or top-level expression) is parsed; bindings in phase level 1 constitute the transformer environment. Phase level -1 corresponds to the run time of a different module for which the enclosing module is imported for use at phase level 1 (relative to the importing module); bindings in phase level -1 constitute the template environment. The label phase level does not correspond to any execution time; it is used to track bindings (e.g., to identifiers within documentation) without implying an execution dependency.

An identifier can have different bindings in different phase levels. More precisely, the scope set associated with a form can be different at different phase levels; a top-level or module context implies a distinct scope at every phase level, while scopes from macro expansion or other syntactic forms are added to a form’s scope sets at all phases. The context of each binding and reference determines the phase level whose scope set is relevant.