Files
tree-sitter-lscript/docs/cst.md
T
2026-10-23 02:13:30 -04:00

151 lines
7.0 KiB
Markdown

# 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:
- `name` for a named declaration;
- `pattern` for a destructuring declaration;
- `binder` for the type parameters of a generic function declaration;
- `type` for an explicit type;
- `value` for 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_literal` and `wrapper_pattern` are gone, and `some`, `ok`, `err`, and
`nil` are no longer keywords; optionals and results use `sum_constructor`
and `sum_pattern`.
- A same-line pipeline is an operand of prefix and infix operators rather than
taking an operator expression as its `input`.
- `pub_declaration` contains `pub_entry` nodes instead of bare identifiers.
The Tree-sitter tree is designed for source tooling and error recovery; it does
not perform semantic validation.