17.2 Spaces
An identifier can have different meanings in different contexts, such as expression versus binding, because an identifier can be bound in a specific space. Example spaces include expr and bind, which generally correspond to a namespace that provides a binding form for the space. Binding forms like def, expr.macro, and bind.macro bind an identifier in the corresponding space.
Forms that bind in different spaces can be used on the same name to give that name a meaning in multiple contexts. The class form binds the class name in many spaces at once, so that a class name works as a constructor in expressions, as an attribute, as a pattern form in bindings, and so on. New bindings in new spaces can always be added alongside existing bindings. The import form supports space-specific operations through the only_space and except_space modifiers, so existing bindings can be suppressed and then, perhaps, replaced on re-export.
Most contexts have their own spaces, even though some of them also overlap with expression positions, such as definitions, declarations, class clauses, and for clauses. The parsing process for such overlapping spaces will check non-expression spaces for bindings, first. The space for expression is special in another way: a binding in that space hides any binding for another space in an enclosing scope (but not bindings in other spaces in the same scope).
The space.enforest and space.transform forms create a new space along with its associated parser driver and macro-definitions forms.
declaration | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Besides being defined as a space, space_id is defined as a namespace. Among the space_clause_or_body_or_exports, nestable_body forms can add definitions and exports to the namespace, the same as for namespace. However, the namespace is particularly intended to export the name specified by macro_definer. That name is conventionally macro. As a somewhat lower-level mechanism, bridge_definer exports a name for binding arbitrary compile-time values analogous to meta.bridge. If macro_definer and bridge_definer are not declared, then there is no way to bind in the new namespace except by using lower-level mechanisms.
Also typically among the space_clause_or_body_or_exports, a meta_namespace declares the name of a compile-time namespace, typically used in macros. The meta namespace’s name is conventionally _meta appended to the end of the main namespace’s name. The space_meta_clause_or_body forms in the body of a meta_namespace clause are implicitly shifted to compile time, as if wrapped by meta. The meta namespace is particularly intended to export syntax classes with names specified by parse_syntax_class, parse_prefix_more_syntax_class, and parse_infix_more_syntax_class clauses. If no name is declared with parse_syntax_class or similar, then there is no way to parse terms in the new space except by lower-level mechanisms.
All together, a typical use of space.enforest includes at least space_path, macro_definer, and meta_namespace containing parse_syntax_class.
space.enforest new_thing:
space_path my_collection/new_thing
macro_definer macro
meta_namespace new_thing_meta:
parse_syntax_class Parsed
These pieces might be used by an expression macro for a form that has a “new thing” position, while an operator is meanwhile defined to work in the “new thing” space.
expr.macro 'print_new_thing: $(thing :: new_thing_meta.Parsed)':
new_thing.macro 'the':
'"the new thing"'
> print_new_thing: the
the new thing
The identifier supplied for parse_syntax_class is defined as a ~group syntax class with a group field. The syntax class triggers parsing of a group using macros bindings, which are defined using the identifier bound by macro_definer. For a pattern variable using the syntax class bound by parse_syntax_class, its value is the result of parsing, while the group field holds the terms that were parsed. No constraints are imposed on the result of parsing, except that it must be represented as a syntax object.
The parse_syntax_class is used with a name followed by a parenthesized sequence of names, the syntax class requires arguments when used in a syntax pattern. Those arguments are propagated to a macro for the new space, which can receive the arguments through declarations that use the keywords listed with macro_definer. The number of keywords listed with macro_definer must match the number of arguments specified with parse_syntax_class.
When just macro_definer and parse_syntax_class are declared, then each macro effectively must produce a fully parsed term. To enable macros in the space that expand to uses of other macros in the space, a distinction is needed between fully parsed and still-to-be-expanded terms. Use parsed_packer and parsed_unpacker to introduce that distinction, use the packer name in macros that produce fully parsed terms, and use the unpacker name to access parsed content. Meanwhile, the syntax classes bound by parse_syntax_class and similar recognize terms constructed via parsed_packer as already parsed.
space.enforest newer_thing:
space_path my_collection/new_thing
macro_definer macro
meta_namespace newer_thing_meta:
parse_syntax_class Parsed
parsed_packer pack
parsed_unpacker unpack
expr.macro 'print_newer_thing: $(thing :: newer_thing_meta.Parsed)':
> print_newer_thing: the
the newer thing
> print_newer_thing: THE
the newer thing
When a parse_checker clause is supplied, then it can impose a check and/or conversion on the result for every macro in the space. That conversion should include recursively expanding when the result is not yet fully expanded, which can be detected by supplying a second argument to the function bound by parsed_unpacker. The default parse checker performs this recursive parsing step.
More details on space clauses and meta clauses:
macro_definer: declares an identifier to be bound to a macro-definition form analogous to expr.macro, but for defining macros for the space. Keywords listed after the identifier serve as options along the same lines as ~op_stx in expr.macro, and they receive arguments passed to the syntax class as declared by parse_syntax_class.
bridge_definer: declares an identifier to be bound to a meta-definition form analogous to meta.bridge, but for defining bridges in the space.
parse_syntax_class: declares an identifier to be bound as a ~group syntax class with a group field; the value of a match is a parsed term, while the group field holds the matched unparsed terms. Identifiers listed in parentheses after the syntax class name are arguments to the syntax class, and they are received by macro implementations through keywords listed in macro_definer. The number of arguments declared in parse_syntax_class must match the number of keywords listed in macro_definer, and the arguments and keywords are correlated by position.
parse_prefix_more_syntax_class: declares an identifier to be bound as a ~group syntax class that takes one argument and has group and tail fields. The argument is a syntax object containing a prefix operator or identifier that is bound for the space. Parsing proceeds as if after the argument of the operator, which means that parsing can stop with a tail sequence remaining. The parsed “argument” is is the matched result, the consumed terms are in a group field, and the remaining tail is a tail repetition field.
parse_infix_more_syntax_class: declares an identifier like parse_prefix_more_syntax_class, but the syntax class expects a syntax object with an infix operator or identifier. Parsing can stop when reaching an infix operator in the group whose precedence is weaker than the starting one.
name_start_syntax_class: declares an identifier to be bound as a ~group syntax class, which has name, head, and tail fields. The syntax class matches a group that starts with a dotted name that is bound in the space, and the name is converted to a single identifier term as the name field. It also matches an unbound name as the start of the group. The head field is a repetition that contains the leading terms of the group that we used to compute the name, and the tail field is a repetition that contains the remaining terms of the group.
parse_checker: supplies a compile-time function that is applied to two arguments: the result of any macro defined for the space, and a function implementing the macro transformer (which is useful for reporting errors or recursively expanding); the result is a syntax object, typically the one that was given, but possibly adjusted. The default checker recursively expands when either parsed_packer or parsed_unpacker is supplied.
parsed_packer: declares an identifier to be bound to a function that takes a syntax term and returns a syntax object representing a parsed term. A parsed term parses as itself, and it is opaque except as unpacked via a function declared with parsed_unpacker.
parsed_unpacker: declares an identifier to be bound to a function that takes a syntax term and optionally either #false or a function of one argument. If the first argument is a parsed term, the declared unpacker acts as the inverse of the function declared with parsed_packer. For any other value, if a second argument is provided as a function, then the function is called and the first argument is passed along; otherwise, an error is reported.
identifier_parser: supplies a compile-time function that is applied to an identifier that is not bound in the space and should return a parsed form for the identifier. By default, a syntax error is reported for unbound identifiers.
description: supplies a string that describes the space; the string is used for reporting syntax errors.
operator_description: supplies a string that describes operators in the space; the string is used for reporting syntax errors.
reflection: declares an identifier to be bound to a SpaceMeta that refers to the defined space. This name is useful in combination with syntax_meta.value, for example.
declaration | |||
|
A space.transform declaration does not support parse_prefix_more_syntax_class, parse_infix_more_syntax_class, identifier_parser, or operator_description clauses.
space clause | |||
| |||
space clause | |||
| |||
space clause | |||
| |||
| |||
space clause | |||
| |||
space clause | |||
|
space meta clause | |
| |
space meta clause | |
| |
| |
space meta clause | |
| |
space meta clause | |
| |
space meta clause | |
| |
space meta clause | |
| |
space meta clause | |
| |
space meta clause | |
| |
space meta clause | |
| |
space meta clause | |
| |
space meta clause | |
| |
space meta clause | |
Clause forms for use within a meta_namespace clause within a space.enforest or space.transform form. See space.enforest for more information.
annotation | |
A SpaceMeta compile-time value reflects a space that would be referenced in a run-time position by the space name. For example, expr_meta.space for use with a compile-time function like syntax_meta.value refers to the same space as expr as used with only_space.