7.0 KiB
Concrete syntax tree contract
This document describes the named nodes and fields intended for consumers such
as editor integrations, formatters, and structural search. Rules prefixed with _
and rules listed as Tree-sitter supertypes are implementation details and do not
appear as named wrapper nodes in ordinary trees.
Source and declarations
source_file contains declarations, imports, publication declarations, and
standalone expressions directly. The choice-only module_item, expression,
and value_expression rules are hidden supertypes.
declaration uses these fields:
namefor a named declaration;patternfor a destructuring declaration;binderfor the type parameters of a generic function declaration;typefor an explicit type;valuefor an initializer.
The punctuation distinguishes immutable, mutable, typed, and deferred forms. Consumers should inspect that punctuation rather than infer the form from the set of fields alone.
A binder holds one binder_entry per parameter or bound. Each entry exposes
type and, after :, an optional constraint, which is a constraint name or
an inline constraint_body. A constraint_body (<>{ ... }) also appears as
the whole initializer of a :: declaration, inside type_definition. It
contains constraint_entry nodes with name and output fields and
constraint_spread nodes with a constraint field.
Modules
use_declaration contains a module_path and either an alias, an
import_filter, or import_member nodes. An import_member exposes name
and either alias or an import_filter. import_filter wraps the for
clause and exposes its type. pub_declaration contains pub_entry nodes,
each with a name and an optional import_filter.
Expressions
Only expressions that actually contain an operator produce an operator node:
| Node | Fields |
|---|---|
assignment_expression |
left, operator, right |
range_expression |
left, operator, right |
binary_expression |
left, operator, right |
unary_expression |
operator, operand |
postfix_expression |
value, repeated operator; member and index operators additionally expose member or index |
pipeline_expression |
input, followed by stage nodes |
Precedence-only rules are hidden. For example, a + b * -c has one outer and
one inner binary_expression, then a unary_expression; it does not contain a
wrapper for every precedence level.
Pipeline stages are postfix. A same-line stage binds to the operand
immediately to its left, tighter than every prefix and infix operator, so
-x->abs is a unary_expression whose operand is a pipeline_expression,
and a + b->f is a binary_expression whose right operand is one. A
pipeline_expression is one flat node: its input is followed by every stage
that applies to it, same-line and continuation alike. A leading-operator
continuation applies to the complete ordinary expression on the preceding
line, so in a + b->f followed by a continuation ->g the outer
pipeline_expression has the binary_expression as its input. A negative
literal is never a stage input: -3->f negates the pipeline, while a bare -3
remains one signed_numeric_literal.
Literal leaves are named. Boolean values use boolean_literal; numeric,
string, character, and unit literals retain their specific nodes.
default_expression is also named.
Optionals and results are ordinary sums. |.none is a sum_constructor, and
value->|.some, value->|.ok, and error->|.err are pipelines whose stage
callable is a sum_constructor. sum_constructor exposes an optional type
and the variant. The former some, ok, err, and nil keywords are
ordinary identifiers. Postfix value! and prefix !error are not accepted.
function_literal takes one input and owns the body field. A pattern with
one annotation for the whole input, such as @ value: i32 or
@ (left, right): (i32, i32), appears as parameter: (typed_parameter).
The annotation is optional: an input whose type comes from the expected
function type, such as @ value or @ (left, right), is also a
typed_parameter without a type field. Component-annotated tuple
destructuring, such as @ (left: i32, right), appears as
parameter: (typed_tuple_pattern). A typed_tuple_pattern contains at least
two typed_tuple_component nodes, each with an optional type. These bind
components of one tuple input, not separate function arguments. () matches
unit and is a typed_parameter over a unit_pattern; singleton tuple patterns
are invalid. Bindings expose pattern and, when annotated, type; a header
may also expose effect (-> or =>) and result.
Patterns
sum_pattern matches a variant, including |.none, |.some pattern,
|.ok pattern, and |.err pattern. It exposes an optional type, the
variant, and an optional payload pattern atom. bits_pattern exposes the
optional type as well.
Types
Meaningful type constructors are named directly:
| Node | Fields |
|---|---|
function_type |
input, effect, result |
result_type |
value, error |
optional_type |
value |
array_type |
element |
map_type |
key, value |
grouped_type |
value |
type_application |
constructor followed by its type arguments |
output_type |
constructor, function, output |
output_type is the Output<f: U> spelling: a type name applied to a
function name and a type. The grammar does not check that the constructor is
spelled Output. Primitive, unit, tuple, product, sum, and bits types retain
their corresponding named nodes. Internal primary/container/result categories
are hidden.
Trivia and physical lines
Comments are named extra nodes. Spaces, tabs, and structural newlines remain in the source gaps between nodes, so a formatter must retain the original source alongside the tree. Leading-operator and pipeline continuations use composite tokens containing their newline; this prevents a scope separator from stealing a valid continuation and preserves the language's physical-line rules.
The future-reserved word context is a reserved_word token that no
production accepts, so it is rejected wherever an identifier is expected.
Compatibility
Adding nodes or fields is compatible. Removing or renaming a named node or field, changing its meaning, or changing which source construct owns it is a breaking CST change. Until the package reaches 1.0, such changes require corpus snapshot updates and a version change, and must be called out in release notes.
Version 0.2.0 made these breaking changes:
nil_literalandwrapper_patternare gone, andsome,ok,err, andnilare no longer keywords; optionals and results usesum_constructorandsum_pattern.- A same-line pipeline is an operand of prefix and infix operators rather than
taking an operator expression as its
input. pub_declarationcontainspub_entrynodes instead of bare identifiers.
The Tree-sitter tree is designed for source tooling and error recovery; it does not perform semantic validation.