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

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:

  • 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.