This directory contains a Tree-sitter grammar for STLC++ (called stlcpp in this repo). It is primarily intended to be consumed by the Zed extension in the parent repository (stlcpp-zed/).
The grammar is designed for editor use:
- stable parsing (minimize cascading
ERRORnodes) - explicit named nodes for punctuation/operators (to support robust highlighting without literal-token queries)
- a pragmatic approximation of the upstream grammar to keep Zed features usable
This grammar aims to follow the upstream STLC++ surface syntax (see the upstream nom parser in aalto-opencs/stlcpp), including:
-
Value declarations:
x : Typex = term- Common pattern:
main : Intmain = ...
- In this Tree-sitter grammar, these may be represented as a single
declarationnode with an optional embedded definition.
-
Type alias declarations:
State : TypeState = <type>
-
Import statements:
import a.b(module paths are parsed as term identifiers in this editor grammar)
-
Custom syntax declarations:
infixl,infixr,prefix(as used by upstream syntax extensions)
- Variables / identifiers:
- term identifiers start with lowercase and may contain
.,_,'
- term identifiers start with lowercase and may contain
- Literals:
- integers (decimal)
- booleans:
true,false - unit:
() - character literals and string literals (with upstream-style escapes, including
\u{...})
- Let:
let x = t in u
- If:
if t then u else v
- Functions:
- term abstraction:
fun x : T, t - type abstraction:
fun T, t(with optional sort annotation)
- term abstraction:
- Sums:
- injections:
inl t T,inr t T - case:
case t of | inl x => u | inr y => v(optional braces supported)
- injections:
- Lists:
nil T- list literals:
[t1, t2, ... : Type] - list case:
lcase t of | nil => u | cons x xs => v(optional braces supported)
- Applications:
f x y(application chains)
- Custom symbolic operators:
- tokenized similarly to upstream βsymbolicβ operators, excluding structural punctuation
=is treated as structural and is not a custom operator
- Named types / variables (uppercase-initial) and
_hole type - Parenthesized types and products:
(T)(A, B)
- List type sugar:
[T]
- Sum types (left-associative):
A + B[Char] + (Unit, [Char])
- Type constructors:
List T,IO T
- Universal quantification:
forall T, TypeExpr
- Right-associative arrows:
A -> B -> C- Note:
A -> B + C -> Dparses asA -> (B + C) -> D(arrow binds looser than sum)
Only line comments are supported:
// ...
This matches the upstream STLC++ parser (there are no -- comments).
The C parser and metadata files under src/ are generated from grammar.js:
src/parser.c- Generated C parser implementationsrc/node-types.json- Node type definitionssrc/grammar.json- Compiled grammar metadata
These files are checked into the repository for convenience and to support direct use by editor extensions.
To regenerate after editing grammar.js:
tree-sitter generateThen commit the updated src/ files along with your grammar.js changes.
This repo includes Tree-sitter query files under queries/ (e.g. queries/highlights.scm).
Important rules for editor integrations:
- Queries must only reference node types that actually exist in the compiled grammar.
- Avoid literal-token patterns in queries when targeting Zed; prefer named token nodes such as
(equals),(lparen),(rparen), etc.
A package.nix is provided for building the grammar with Nix:
nix-build -A packages.tree-sitter-stlcppThe Nix package compiles the C parser and installs:
- Shared library (
parser.so) - Grammar metadata files for tooling
- Runs test suite during build (see Testing below)
For Zed extensions, reference this repository directly:
[grammars.stlcpp]
repository = "https://github.com/aalto-opencs/tree-sitter-stlcpp"
rev = "xxx" # Use a commit SHA from the main branchThe repository includes all necessary files for Zed's build process:
- Source grammar (
grammar.js) - Generated parser files (
src/parser.c,src/grammar.json,src/node-types.json) - Query files (
queries/highlights.scm, etc.)
The test/ directory contains comprehensive tests using tree-sitter's standard corpus format:
The test suite in test/corpus/ uses tree-sitter's standard corpus format:
- 53 test cases covering all major language features
- Tests verify both parsing and AST structure
- Organized by feature: applications, functions, types, operators, etc.
Run tests:
tree-sitter testUpdate expected trees after grammar changes:
tree-sitter test --updateManual testing with upstream examples:
# Parse a specific file
tree-sitter parse ../stlcpp/examples/var.stlc
# Test all upstream examples
for file in ../stlcpp/examples/*.stlc; do
tree-sitter parse "$file" 2>&1 | grep -q ERROR && echo "β $file" || echo "β $file"
doneSee test/README.md for complete testing documentation.
Typical workflow:
- Edit
grammar.js - Run
tree-sitter generateto regeneratesrc/files - Validate:
tree-sitter test(run all 53 corpus tests)tree-sitter parse <file.stlc>(inspect parse tree for specific files)- Test upstream examples if making significant changes
- Update corpus tests if needed:
tree-sitter test --update - Commit your changes:
grammar.js(your grammar edits)src/(regenerated parser files)test/corpus/(if tests were added/updated)
- Push to
main
Note: Always run tree-sitter generate and commit the updated src/ files when you modify grammar.js. This ensures the repository stays in sync.
- Upstream STLC++ implementation and parser: https://github.com/aalto-opencs/stlcpp
- Tree-sitter: https://tree-sitter.github.io/tree-sitter/