From b31922cbec08ba96c71c1f84d6b909b019d8f762 Mon Sep 17 00:00:00 2001
From: 0xisk <0xisk@proton.me>
Date: Tue, 15 Sep 2026 08:55:04 +0200
Subject: [PATCH 01/14] feat(tree-sitter-compact): add declaration-level
Compact grammar
Adds a Cargo workspace and a tree-sitter grammar that resolves every
Compact declaration to a named node while leaving bodies, parameter
lists and expressions opaque. The grammar exists to feed a doc-comment
linter, so precision stops at the declaration boundary and the full
expression grammar is out of scope.
* grammar: fourteen declaration kinds with `name` fields, recursive
module bodies, three comment kinds as extras, structured type
expressions so nested generic arguments resolve.
* binding: native Rust binding, generated parser committed, CI fails
when regeneration changes it.
* tests: tree-sitter corpus plus a Rust corpus test that rejects any
ERROR or MISSING node over the upstream formatter fixtures, the
in-repo Compact sources, and an optional external tree.
---
.github/workflows/rust.yml | 46 +
Cargo.lock | 216 +
Cargo.toml | 17 +
biome.json | 1 +
crates/tree-sitter-compact/Cargo.toml | 34 +
crates/tree-sitter-compact/README.md | 88 +
.../bindings/rust/build.rs | 19 +
.../tree-sitter-compact/bindings/rust/lib.rs | 34 +
crates/tree-sitter-compact/grammar.js | 256 +
crates/tree-sitter-compact/package.json | 16 +
.../queries/highlights.scm | 42 +
crates/tree-sitter-compact/src/grammar.json | 1380 ++++
.../tree-sitter-compact/src/node-types.json | 913 +++
crates/tree-sitter-compact/src/parser.c | 7042 +++++++++++++++++
.../src/tree_sitter/alloc.h | 54 +
.../src/tree_sitter/array.h | 291 +
.../src/tree_sitter/parser.h | 286 +
.../test/corpus/circuits-and-constructors.txt | 152 +
.../test/corpus/comments.txt | 134 +
.../test/corpus/imports.txt | 141 +
.../test/corpus/modules-and-exports.txt | 116 +
.../test/corpus/pragma-and-include.txt | 37 +
.../test/corpus/structs-enums-contracts.txt | 105 +
.../test/corpus/types-ledger-witness.txt | 149 +
.../test/fixtures/upstream/NOTICE | 17 +
.../upstream/already_formatted.compact | 26 +
.../test/fixtures/upstream/example_1.compact | 34 +
.../test/fixtures/upstream/example_10.compact | 62 +
.../test/fixtures/upstream/example_11.compact | 32 +
.../test/fixtures/upstream/example_12.compact | 36 +
.../test/fixtures/upstream/example_13.compact | 185 +
.../test/fixtures/upstream/example_2.compact | 40 +
.../test/fixtures/upstream/example_3.compact | 41 +
.../test/fixtures/upstream/example_4.compact | 32 +
.../test/fixtures/upstream/example_5.compact | 34 +
.../test/fixtures/upstream/example_6.compact | 31 +
.../test/fixtures/upstream/example_7.compact | 32 +
.../test/fixtures/upstream/example_8.compact | 43 +
.../test/fixtures/upstream/example_9.compact | 34 +
crates/tree-sitter-compact/tests/corpus.rs | 214 +
crates/tree-sitter-compact/tree-sitter.json | 37 +
rust-toolchain.toml | 3 +
42 files changed, 12502 insertions(+)
create mode 100644 .github/workflows/rust.yml
create mode 100644 Cargo.lock
create mode 100644 Cargo.toml
create mode 100644 crates/tree-sitter-compact/Cargo.toml
create mode 100644 crates/tree-sitter-compact/README.md
create mode 100644 crates/tree-sitter-compact/bindings/rust/build.rs
create mode 100644 crates/tree-sitter-compact/bindings/rust/lib.rs
create mode 100644 crates/tree-sitter-compact/grammar.js
create mode 100644 crates/tree-sitter-compact/package.json
create mode 100644 crates/tree-sitter-compact/queries/highlights.scm
create mode 100644 crates/tree-sitter-compact/src/grammar.json
create mode 100644 crates/tree-sitter-compact/src/node-types.json
create mode 100644 crates/tree-sitter-compact/src/parser.c
create mode 100644 crates/tree-sitter-compact/src/tree_sitter/alloc.h
create mode 100644 crates/tree-sitter-compact/src/tree_sitter/array.h
create mode 100644 crates/tree-sitter-compact/src/tree_sitter/parser.h
create mode 100644 crates/tree-sitter-compact/test/corpus/circuits-and-constructors.txt
create mode 100644 crates/tree-sitter-compact/test/corpus/comments.txt
create mode 100644 crates/tree-sitter-compact/test/corpus/imports.txt
create mode 100644 crates/tree-sitter-compact/test/corpus/modules-and-exports.txt
create mode 100644 crates/tree-sitter-compact/test/corpus/pragma-and-include.txt
create mode 100644 crates/tree-sitter-compact/test/corpus/structs-enums-contracts.txt
create mode 100644 crates/tree-sitter-compact/test/corpus/types-ledger-witness.txt
create mode 100644 crates/tree-sitter-compact/test/fixtures/upstream/NOTICE
create mode 100644 crates/tree-sitter-compact/test/fixtures/upstream/already_formatted.compact
create mode 100644 crates/tree-sitter-compact/test/fixtures/upstream/example_1.compact
create mode 100644 crates/tree-sitter-compact/test/fixtures/upstream/example_10.compact
create mode 100644 crates/tree-sitter-compact/test/fixtures/upstream/example_11.compact
create mode 100644 crates/tree-sitter-compact/test/fixtures/upstream/example_12.compact
create mode 100644 crates/tree-sitter-compact/test/fixtures/upstream/example_13.compact
create mode 100644 crates/tree-sitter-compact/test/fixtures/upstream/example_2.compact
create mode 100644 crates/tree-sitter-compact/test/fixtures/upstream/example_3.compact
create mode 100644 crates/tree-sitter-compact/test/fixtures/upstream/example_4.compact
create mode 100644 crates/tree-sitter-compact/test/fixtures/upstream/example_5.compact
create mode 100644 crates/tree-sitter-compact/test/fixtures/upstream/example_6.compact
create mode 100644 crates/tree-sitter-compact/test/fixtures/upstream/example_7.compact
create mode 100644 crates/tree-sitter-compact/test/fixtures/upstream/example_8.compact
create mode 100644 crates/tree-sitter-compact/test/fixtures/upstream/example_9.compact
create mode 100644 crates/tree-sitter-compact/tests/corpus.rs
create mode 100644 crates/tree-sitter-compact/tree-sitter.json
create mode 100644 rust-toolchain.toml
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
new file mode 100644
index 0000000..2bc8283
--- /dev/null
+++ b/.github/workflows/rust.yml
@@ -0,0 +1,46 @@
+name: Rust
+
+on:
+ pull_request:
+ push:
+ branches:
+ - main
+ - beta
+
+jobs:
+ rust:
+ name: Run Rust Checks
+ runs-on: ubuntu-24.04
+
+ permissions:
+ contents: read
+
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
+ with:
+ egress-policy: audit
+
+ - name: Check out code
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+
+ - name: Install the pinned toolchain
+ run: |
+ rustup toolchain install 1.96 --profile minimal --component rustfmt --component clippy
+ rustup show active-toolchain
+
+ - name: Check formatting
+ run: cargo fmt --all --check
+
+ - name: Lint
+ run: cargo clippy --workspace --all-targets -- -D warnings
+
+ - name: Test
+ run: cargo test --workspace
+
+ - name: Check the generated parser is up to date
+ working-directory: crates/tree-sitter-compact
+ run: npx -y tree-sitter-cli@0.25.8 generate
+
+ - name: Fail if regeneration changed the parser
+ run: git diff --exit-code -- crates/tree-sitter-compact/src
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..e78a9e5
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,216 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "cc"
+version = "1.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
+name = "indexmap"
+version = "2.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
+[[package]]
+name = "memchr"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
+
+[[package]]
+name = "serde"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.151"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "shlex"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+
+[[package]]
+name = "streaming-iterator"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
+
+[[package]]
+name = "syn"
+version = "3.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tree-sitter"
+version = "0.25.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78f873475d258561b06f1c595d93308a7ed124d9977cb26b148c2084a4a3cc87"
+dependencies = [
+ "cc",
+ "regex",
+ "regex-syntax",
+ "serde_json",
+ "streaming-iterator",
+ "tree-sitter-language",
+]
+
+[[package]]
+name = "tree-sitter-compact"
+version = "0.1.0"
+dependencies = [
+ "cc",
+ "tree-sitter",
+ "tree-sitter-language",
+]
+
+[[package]]
+name = "tree-sitter-language"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca0d1bf6fdd806e43ae5198f82f527056d359def39e54e67a0f478ac09dac081"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "zmij"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..af07435
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,17 @@
+[workspace]
+resolver = "2"
+members = ["crates/*"]
+
+[workspace.package]
+edition = "2024"
+license = "MIT"
+repository = "https://github.com/OpenZeppelin/compact-tools"
+rust-version = "1.96"
+
+[workspace.lints.clippy]
+pedantic = { level = "warn", priority = -1 }
+
+[workspace.dependencies]
+cc = "1"
+tree-sitter = "0.25"
+tree-sitter-language = "0.1"
diff --git a/biome.json b/biome.json
index 79dd4db..88b7ec7 100644
--- a/biome.json
+++ b/biome.json
@@ -13,6 +13,7 @@
"!**/tsconfig.base.json",
"!**/tsconfig*.json",
"!**/*.compact",
+ "!crates/tree-sitter-compact/src/**/*",
"!**/artifacts/**/*",
"!**/coverage/**/*",
"!**/dist/**/*",
diff --git a/crates/tree-sitter-compact/Cargo.toml b/crates/tree-sitter-compact/Cargo.toml
new file mode 100644
index 0000000..3626181
--- /dev/null
+++ b/crates/tree-sitter-compact/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "tree-sitter-compact"
+version = "0.1.0"
+description = "Declaration-level Compact grammar for tree-sitter"
+build = "bindings/rust/build.rs"
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+rust-version.workspace = true
+
+include = [
+ "bindings/rust/*",
+ "grammar.js",
+ "queries/*",
+ "src/*",
+ "tree-sitter.json",
+ "LICENSE*",
+ "README.md",
+]
+
+[lints]
+workspace = true
+
+[lib]
+path = "bindings/rust/lib.rs"
+
+[dependencies]
+tree-sitter-language.workspace = true
+
+[build-dependencies]
+cc.workspace = true
+
+[dev-dependencies]
+tree-sitter.workspace = true
diff --git a/crates/tree-sitter-compact/README.md b/crates/tree-sitter-compact/README.md
new file mode 100644
index 0000000..287c198
--- /dev/null
+++ b/crates/tree-sitter-compact/README.md
@@ -0,0 +1,88 @@
+# tree-sitter-compact
+
+Declaration-level [tree-sitter](https://tree-sitter.github.io) grammar for the
+Compact smart-contract language (Midnight), tracking language version 0.26.
+
+It exists to feed a doc-comment linter, so it resolves declarations precisely and
+leaves everything else opaque.
+
+## What it covers
+
+- Every top-level and module-level declaration is a named node with a `name`
+ field: `pragma_declaration`, `include_declaration`, `import_declaration`,
+ `export_declaration`, `module_declaration`, `struct_declaration`,
+ `enum_declaration`, `contract_declaration`, `implements_declaration`,
+ `type_declaration`, `ledger_declaration`, `witness_declaration`,
+ `constructor_declaration`, `circuit_declaration`.
+- `module_declaration` recurses: a `module_body` holds the same declarations.
+- Comments are named nodes in `extras`: `line_comment`, `block_comment`,
+ `doc_comment`.
+- Types are structured enough to walk: `type_expression`, `generic_arguments`,
+ `generic_parameters`, `generic_parameter`.
+- All import forms: bare module, quoted path, generic arguments, `prefix`, and
+ the `{ a, b as c } from` selection.
+
+## What is opaque
+
+- Circuit and constructor bodies: `block` is a brace-balanced token run. Strings
+ and comments are lexed first, so braces inside them do not count.
+- Struct, enum and external-contract bodies reuse the same `block`.
+- Parameter lists: `parameter_list` is a paren-balanced run, so destructuring
+ patterns parse without a pattern grammar.
+- Statements and expressions. There is no expression grammar and none is planned.
+- Version expressions: `version_constraint` is one token that runs to the `;`.
+
+Angle brackets are balanced only inside `generic_arguments`, where a `>` always
+closes a type-argument list. They are never balanced in opaque runs.
+
+## Modifiers
+
+`export`, `sealed`, `pure` and `new` stay anonymous keyword tokens directly under
+the declaration node. A consumer tests for them by walking children and comparing
+`node.kind()`:
+
+```rust
+let exported = {
+ let mut cursor = declaration.walk();
+ declaration
+ .children(&mut cursor)
+ .any(|child| child.kind() == "export")
+};
+```
+
+They are not fields, because a field would need a distinct name per modifier and
+the linter only ever asks whether one is present.
+
+## Comment disambiguation
+
+`doc_comment` matches `/** … */` and outranks `block_comment` by lexical
+precedence when both match the same span. `/**/` matches only `block_comment`,
+since the doc pattern needs at least one `*` after the opener.
+
+## Regenerating the parser
+
+`src/parser.c`, `src/grammar.json`, `src/node-types.json` and `src/tree_sitter/*`
+are generated and committed. After editing `grammar.js`:
+
+```sh
+npx -y tree-sitter-cli@0.25.8 generate
+```
+
+CI regenerates and fails on any diff under `crates/tree-sitter-compact/src`.
+
+## Tests
+
+```sh
+# Grammar corpus, run from this directory.
+npx -y tree-sitter-cli@0.25.8 test
+
+# Rust bindings plus the fixture corpora.
+cargo test --workspace
+
+# Add an external tree of .compact files.
+COMPACT_CORPUS_DIR=/path/to/contracts cargo test --workspace -- --nocapture corpus
+```
+
+`tests/corpus.rs` asserts that no fixture produces an `ERROR` or `MISSING` node.
+`test/fixtures/upstream/` holds the Compact formatter's own examples; see the
+`NOTICE` beside them for provenance.
diff --git a/crates/tree-sitter-compact/bindings/rust/build.rs b/crates/tree-sitter-compact/bindings/rust/build.rs
new file mode 100644
index 0000000..c65afd8
--- /dev/null
+++ b/crates/tree-sitter-compact/bindings/rust/build.rs
@@ -0,0 +1,19 @@
+fn main() {
+ let src_dir = std::path::Path::new("src");
+
+ let mut config = cc::Build::new();
+ config.std("c11").include(src_dir);
+ config.file(src_dir.join("parser.c"));
+
+ // Generated code trips these on some compiler versions; they are not actionable here.
+ config.flag_if_supported("-Wno-unused-but-set-variable");
+ config.flag_if_supported("-Wno-unused-parameter");
+ config.flag_if_supported("-Wno-trigraphs");
+
+ config.compile("tree-sitter-compact");
+
+ println!("cargo:rerun-if-changed=src/parser.c");
+ println!("cargo:rerun-if-changed=src/grammar.json");
+ println!("cargo:rerun-if-changed=src/node-types.json");
+ println!("cargo:rerun-if-changed=src/tree_sitter");
+}
diff --git a/crates/tree-sitter-compact/bindings/rust/lib.rs b/crates/tree-sitter-compact/bindings/rust/lib.rs
new file mode 100644
index 0000000..601c974
--- /dev/null
+++ b/crates/tree-sitter-compact/bindings/rust/lib.rs
@@ -0,0 +1,34 @@
+//! Declaration-level tree-sitter grammar for Compact (Midnight).
+//!
+//! Declarations are named nodes with a `name` field; circuit and constructor
+//! bodies, parameter lists and statements stay opaque brace-balanced runs.
+
+#![deny(unsafe_op_in_unsafe_fn)]
+
+use tree_sitter_language::LanguageFn;
+
+unsafe extern "C" {
+ fn tree_sitter_compact() -> *const ();
+}
+
+/// The tree-sitter language for Compact.
+// SAFETY: `tree_sitter_compact` is generated by tree-sitter-cli into `src/parser.c`,
+// which `build.rs` links into this crate, and it returns a `'static` language pointer.
+pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_compact) };
+
+/// The node-type metadata that tree-sitter-cli generated alongside the parser.
+pub const NODE_TYPES: &str = include_str!("../../src/node-types.json");
+
+/// The highlight query shipped with the grammar.
+pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm");
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn language_loads_into_a_parser() {
+ let mut parser = tree_sitter::Parser::new();
+ parser
+ .set_language(&super::LANGUAGE.into())
+ .expect("the generated parser matches the tree-sitter ABI this crate builds against");
+ }
+}
diff --git a/crates/tree-sitter-compact/grammar.js b/crates/tree-sitter-compact/grammar.js
new file mode 100644
index 0000000..55c2d25
--- /dev/null
+++ b/crates/tree-sitter-compact/grammar.js
@@ -0,0 +1,256 @@
+///
+// @ts-check
+
+// Declaration-level grammar for Compact (Midnight), language version 0.26.
+// Every top-level and module-level declaration is a named node carrying a `name`
+// field; bodies, parameter lists and statements stay opaque brace-balanced runs.
+
+/**
+ * Comma-separated list with an optional trailing comma.
+ * @param {RuleOrLiteral} rule
+ */
+function commaSep(rule) {
+ return optional(seq(rule, repeat(seq(',', rule)), optional(',')));
+}
+
+module.exports = grammar({
+ name: 'compact',
+
+ word: ($) => $.identifier,
+
+ extras: ($) => [/\s/, $.doc_comment, $.block_comment, $.line_comment],
+
+ rules: {
+ source_file: ($) => repeat($._declaration),
+
+ _declaration: ($) =>
+ choice(
+ $.pragma_declaration,
+ $.include_declaration,
+ $.import_declaration,
+ $.export_declaration,
+ $.module_declaration,
+ $.struct_declaration,
+ $.enum_declaration,
+ $.implements_declaration,
+ $.contract_declaration,
+ $.type_declaration,
+ $.ledger_declaration,
+ $.witness_declaration,
+ $.constructor_declaration,
+ $.circuit_declaration,
+ ),
+
+ // pragma id version-expr ;
+ pragma_declaration: ($) =>
+ seq('pragma', field('name', $.identifier), $.version_constraint, ';'),
+
+ // Version expressions are not modelled; the run stops at the terminating `;`.
+ version_constraint: (_$) => token(/[^;{}\r\n]+/),
+
+ // include "file" ;
+ include_declaration: ($) => seq('include', field('path', $.string), ';'),
+
+ // import {sel} from? name gargs? prefix? ;
+ import_declaration: ($) =>
+ seq(
+ 'import',
+ optional($.import_selection),
+ field('name', choice($.identifier, $.string)),
+ optional($.generic_arguments),
+ optional($.import_prefix),
+ ';',
+ ),
+
+ import_selection: ($) => seq('{', commaSep($.import_element), '}', 'from'),
+
+ import_element: ($) =>
+ seq(
+ field('name', $.identifier),
+ optional(seq('as', field('alias', $.identifier))),
+ ),
+
+ import_prefix: ($) => seq('prefix', field('prefix', $.identifier)),
+
+ // export { id, ... } ;?
+ export_declaration: ($) =>
+ seq('export', '{', commaSep($.identifier), '}', optional(';')),
+
+ // export? module Name gparams? { declaration... }
+ module_declaration: ($) =>
+ seq(
+ optional('export'),
+ 'module',
+ field('name', $.identifier),
+ optional($.generic_parameters),
+ field('body', $.module_body),
+ ),
+
+ module_body: ($) => seq('{', repeat($._declaration), '}'),
+
+ // export? struct Name gparams? { typed-id... } ;?
+ struct_declaration: ($) =>
+ seq(
+ optional('export'),
+ 'struct',
+ field('name', $.identifier),
+ optional($.generic_parameters),
+ field('body', $.block),
+ optional(';'),
+ ),
+
+ // export? enum Name { id, ... } ;?
+ enum_declaration: ($) =>
+ seq(
+ optional('export'),
+ 'enum',
+ field('name', $.identifier),
+ field('body', $.block),
+ optional(';'),
+ ),
+
+ // contract implements type ;
+ implements_declaration: ($) =>
+ seq('contract', 'implements', field('type', $.type_expression), ';'),
+
+ // export? contract Name { circuit-declaration... } ;?
+ contract_declaration: ($) =>
+ seq(
+ optional('export'),
+ 'contract',
+ field('name', $.identifier),
+ field('body', $.block),
+ optional(';'),
+ ),
+
+ // export? new? type Name gparams? = type ;
+ type_declaration: ($) =>
+ seq(
+ optional('export'),
+ optional('new'),
+ 'type',
+ field('name', $.identifier),
+ optional($.generic_parameters),
+ '=',
+ field('value', $.type_expression),
+ ';',
+ ),
+
+ // export? sealed? ledger id : type ;
+ ledger_declaration: ($) =>
+ seq(
+ optional('export'),
+ optional('sealed'),
+ 'ledger',
+ field('name', $.identifier),
+ ':',
+ field('type', $.type_expression),
+ ';',
+ ),
+
+ // export? witness id gparams? (params) : type ;
+ witness_declaration: ($) =>
+ seq(
+ optional('export'),
+ 'witness',
+ field('name', $.identifier),
+ optional($.generic_parameters),
+ field('parameters', $.parameter_list),
+ ':',
+ field('type', $.type_expression),
+ ';',
+ ),
+
+ // constructor (params) block
+ constructor_declaration: ($) =>
+ seq(
+ 'constructor',
+ field('parameters', $.parameter_list),
+ field('body', $.block),
+ ),
+
+ // export? pure? circuit name gparams? (params) : type block
+ circuit_declaration: ($) =>
+ seq(
+ optional('export'),
+ optional('pure'),
+ 'circuit',
+ field('name', $.identifier),
+ optional($.generic_parameters),
+ field('parameters', $.parameter_list),
+ ':',
+ field('type', $.type_expression),
+ field('body', $.block),
+ ),
+
+ // <#T, U,>
+ generic_parameters: ($) => seq('<', commaSep($.generic_parameter), '>'),
+
+ generic_parameter: ($) => seq(optional('#'), field('name', $.identifier)),
+
+ // Angle brackets nest only here, where a `>` always closes a type argument list.
+ generic_arguments: ($) => seq('<', repeat($._type_item), '>'),
+
+ type_expression: ($) => repeat1($._type_item),
+
+ _type_item: ($) =>
+ choice(
+ $.identifier,
+ $.number,
+ $.string,
+ $.generic_arguments,
+ $._tuple_type,
+ ',',
+ ':',
+ '..',
+ ),
+
+ _tuple_type: ($) => seq('[', repeat($._type_item), ']'),
+
+ // Brace-balanced opaque run: circuit and constructor bodies, struct, enum and
+ // external-contract bodies, and destructuring patterns inside parameter lists.
+ block: ($) => seq('{', repeat($._opaque), '}'),
+
+ parameter_list: ($) => seq('(', repeat($._opaque), ')'),
+
+ _paren_group: ($) => seq('(', repeat($._opaque), ')'),
+
+ _bracket_group: ($) => seq('[', repeat($._opaque), ']'),
+
+ _opaque: ($) =>
+ choice(
+ $.block,
+ $._paren_group,
+ $._bracket_group,
+ $.string,
+ $._opaque_token,
+ '/',
+ ),
+
+ // Anything that is not a delimiter, a quote or a comment opener.
+ // biome-ignore lint/complexity/noUselessEscapeInRegex: tree-sitter's regex engine rejects an unescaped `[` in a class.
+ _opaque_token: (_$) => token(/[^\s(){}\[\]"'\/]+/),
+
+ identifier: (_$) => /[A-Za-z_$][A-Za-z0-9_$]*/,
+
+ number: (_$) => /[0-9]+/,
+
+ // Double-quoted literals span lines; the Compact formatter emits them that way.
+ string: (_$) =>
+ token(
+ choice(
+ seq('"', repeat(choice(/[^"\\]/, /\\(.|\r?\n)/)), '"'),
+ seq("'", repeat(choice(/[^'\\\r\n]/, /\\./)), "'"),
+ ),
+ ),
+
+ // `/** ... */` outranks `/* ... */` at equal length; `/**/` matches only the latter.
+ doc_comment: (_$) =>
+ token(prec(2, seq('/**', /[^*]*\*+([^/*][^*]*\*+)*/, '/'))),
+
+ block_comment: (_$) =>
+ token(prec(1, seq('/*', /[^*]*\*+([^/*][^*]*\*+)*/, '/'))),
+
+ line_comment: (_$) => token(seq('//', /[^\r\n]*/)),
+ },
+});
diff --git a/crates/tree-sitter-compact/package.json b/crates/tree-sitter-compact/package.json
new file mode 100644
index 0000000..7f85f18
--- /dev/null
+++ b/crates/tree-sitter-compact/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "tree-sitter-compact",
+ "version": "0.1.0",
+ "private": true,
+ "description": "Declaration-level Compact grammar for tree-sitter",
+ "license": "MIT",
+ "main": "grammar.js",
+ "tree-sitter": [
+ {
+ "scope": "source.compact",
+ "file-types": [
+ "compact"
+ ]
+ }
+ ]
+}
diff --git a/crates/tree-sitter-compact/queries/highlights.scm b/crates/tree-sitter-compact/queries/highlights.scm
new file mode 100644
index 0000000..da7e44b
--- /dev/null
+++ b/crates/tree-sitter-compact/queries/highlights.scm
@@ -0,0 +1,42 @@
+; Declaration names.
+(pragma_declaration name: (identifier) @variable.builtin)
+(module_declaration name: (identifier) @type)
+(struct_declaration name: (identifier) @type)
+(enum_declaration name: (identifier) @type)
+(contract_declaration name: (identifier) @type)
+(type_declaration name: (identifier) @type)
+(circuit_declaration name: (identifier) @function)
+(witness_declaration name: (identifier) @function)
+(ledger_declaration name: (identifier) @property)
+(generic_parameter name: (identifier) @type.parameter)
+
+; Keywords and modifiers.
+[
+ "pragma"
+ "include"
+ "import"
+ "export"
+ "from"
+ "as"
+ "prefix"
+ "module"
+ "struct"
+ "enum"
+ "contract"
+ "implements"
+ "type"
+ "new"
+ "sealed"
+ "ledger"
+ "witness"
+ "constructor"
+ "circuit"
+ "pure"
+] @keyword
+
+(string) @string
+(number) @number
+
+(line_comment) @comment
+(block_comment) @comment
+(doc_comment) @comment.documentation
diff --git a/crates/tree-sitter-compact/src/grammar.json b/crates/tree-sitter-compact/src/grammar.json
new file mode 100644
index 0000000..7bc3a42
--- /dev/null
+++ b/crates/tree-sitter-compact/src/grammar.json
@@ -0,0 +1,1380 @@
+{
+ "$schema": "https://tree-sitter.github.io/tree-sitter/assets/schemas/grammar.schema.json",
+ "name": "compact",
+ "word": "identifier",
+ "rules": {
+ "source_file": {
+ "type": "REPEAT",
+ "content": {
+ "type": "SYMBOL",
+ "name": "_declaration"
+ }
+ },
+ "_declaration": {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "pragma_declaration"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "include_declaration"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "import_declaration"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "export_declaration"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "module_declaration"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "struct_declaration"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "enum_declaration"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "implements_declaration"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "contract_declaration"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "type_declaration"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "ledger_declaration"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "witness_declaration"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "constructor_declaration"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "circuit_declaration"
+ }
+ ]
+ },
+ "pragma_declaration": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "pragma"
+ },
+ {
+ "type": "FIELD",
+ "name": "name",
+ "content": {
+ "type": "SYMBOL",
+ "name": "identifier"
+ }
+ },
+ {
+ "type": "SYMBOL",
+ "name": "version_constraint"
+ },
+ {
+ "type": "STRING",
+ "value": ";"
+ }
+ ]
+ },
+ "version_constraint": {
+ "type": "TOKEN",
+ "content": {
+ "type": "PATTERN",
+ "value": "[^;{}\\r\\n]+"
+ }
+ },
+ "include_declaration": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "include"
+ },
+ {
+ "type": "FIELD",
+ "name": "path",
+ "content": {
+ "type": "SYMBOL",
+ "name": "string"
+ }
+ },
+ {
+ "type": "STRING",
+ "value": ";"
+ }
+ ]
+ },
+ "import_declaration": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "import"
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "import_selection"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "FIELD",
+ "name": "name",
+ "content": {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "identifier"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "string"
+ }
+ ]
+ }
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "generic_arguments"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "import_prefix"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "STRING",
+ "value": ";"
+ }
+ ]
+ },
+ "import_selection": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "{"
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "import_element"
+ },
+ {
+ "type": "REPEAT",
+ "content": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": ","
+ },
+ {
+ "type": "SYMBOL",
+ "name": "import_element"
+ }
+ ]
+ }
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": ","
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "STRING",
+ "value": "}"
+ },
+ {
+ "type": "STRING",
+ "value": "from"
+ }
+ ]
+ },
+ "import_element": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "FIELD",
+ "name": "name",
+ "content": {
+ "type": "SYMBOL",
+ "name": "identifier"
+ }
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "as"
+ },
+ {
+ "type": "FIELD",
+ "name": "alias",
+ "content": {
+ "type": "SYMBOL",
+ "name": "identifier"
+ }
+ }
+ ]
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ }
+ ]
+ },
+ "import_prefix": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "prefix"
+ },
+ {
+ "type": "FIELD",
+ "name": "prefix",
+ "content": {
+ "type": "SYMBOL",
+ "name": "identifier"
+ }
+ }
+ ]
+ },
+ "export_declaration": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "export"
+ },
+ {
+ "type": "STRING",
+ "value": "{"
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "identifier"
+ },
+ {
+ "type": "REPEAT",
+ "content": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": ","
+ },
+ {
+ "type": "SYMBOL",
+ "name": "identifier"
+ }
+ ]
+ }
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": ","
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "STRING",
+ "value": "}"
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": ";"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ }
+ ]
+ },
+ "module_declaration": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "export"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "STRING",
+ "value": "module"
+ },
+ {
+ "type": "FIELD",
+ "name": "name",
+ "content": {
+ "type": "SYMBOL",
+ "name": "identifier"
+ }
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "generic_parameters"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "FIELD",
+ "name": "body",
+ "content": {
+ "type": "SYMBOL",
+ "name": "module_body"
+ }
+ }
+ ]
+ },
+ "module_body": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "{"
+ },
+ {
+ "type": "REPEAT",
+ "content": {
+ "type": "SYMBOL",
+ "name": "_declaration"
+ }
+ },
+ {
+ "type": "STRING",
+ "value": "}"
+ }
+ ]
+ },
+ "struct_declaration": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "export"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "STRING",
+ "value": "struct"
+ },
+ {
+ "type": "FIELD",
+ "name": "name",
+ "content": {
+ "type": "SYMBOL",
+ "name": "identifier"
+ }
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "generic_parameters"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "FIELD",
+ "name": "body",
+ "content": {
+ "type": "SYMBOL",
+ "name": "block"
+ }
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": ";"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ }
+ ]
+ },
+ "enum_declaration": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "export"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "STRING",
+ "value": "enum"
+ },
+ {
+ "type": "FIELD",
+ "name": "name",
+ "content": {
+ "type": "SYMBOL",
+ "name": "identifier"
+ }
+ },
+ {
+ "type": "FIELD",
+ "name": "body",
+ "content": {
+ "type": "SYMBOL",
+ "name": "block"
+ }
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": ";"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ }
+ ]
+ },
+ "implements_declaration": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "contract"
+ },
+ {
+ "type": "STRING",
+ "value": "implements"
+ },
+ {
+ "type": "FIELD",
+ "name": "type",
+ "content": {
+ "type": "SYMBOL",
+ "name": "type_expression"
+ }
+ },
+ {
+ "type": "STRING",
+ "value": ";"
+ }
+ ]
+ },
+ "contract_declaration": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "export"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "STRING",
+ "value": "contract"
+ },
+ {
+ "type": "FIELD",
+ "name": "name",
+ "content": {
+ "type": "SYMBOL",
+ "name": "identifier"
+ }
+ },
+ {
+ "type": "FIELD",
+ "name": "body",
+ "content": {
+ "type": "SYMBOL",
+ "name": "block"
+ }
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": ";"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ }
+ ]
+ },
+ "type_declaration": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "export"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "new"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "STRING",
+ "value": "type"
+ },
+ {
+ "type": "FIELD",
+ "name": "name",
+ "content": {
+ "type": "SYMBOL",
+ "name": "identifier"
+ }
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "generic_parameters"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "STRING",
+ "value": "="
+ },
+ {
+ "type": "FIELD",
+ "name": "value",
+ "content": {
+ "type": "SYMBOL",
+ "name": "type_expression"
+ }
+ },
+ {
+ "type": "STRING",
+ "value": ";"
+ }
+ ]
+ },
+ "ledger_declaration": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "export"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "sealed"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "STRING",
+ "value": "ledger"
+ },
+ {
+ "type": "FIELD",
+ "name": "name",
+ "content": {
+ "type": "SYMBOL",
+ "name": "identifier"
+ }
+ },
+ {
+ "type": "STRING",
+ "value": ":"
+ },
+ {
+ "type": "FIELD",
+ "name": "type",
+ "content": {
+ "type": "SYMBOL",
+ "name": "type_expression"
+ }
+ },
+ {
+ "type": "STRING",
+ "value": ";"
+ }
+ ]
+ },
+ "witness_declaration": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "export"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "STRING",
+ "value": "witness"
+ },
+ {
+ "type": "FIELD",
+ "name": "name",
+ "content": {
+ "type": "SYMBOL",
+ "name": "identifier"
+ }
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "generic_parameters"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "FIELD",
+ "name": "parameters",
+ "content": {
+ "type": "SYMBOL",
+ "name": "parameter_list"
+ }
+ },
+ {
+ "type": "STRING",
+ "value": ":"
+ },
+ {
+ "type": "FIELD",
+ "name": "type",
+ "content": {
+ "type": "SYMBOL",
+ "name": "type_expression"
+ }
+ },
+ {
+ "type": "STRING",
+ "value": ";"
+ }
+ ]
+ },
+ "constructor_declaration": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "constructor"
+ },
+ {
+ "type": "FIELD",
+ "name": "parameters",
+ "content": {
+ "type": "SYMBOL",
+ "name": "parameter_list"
+ }
+ },
+ {
+ "type": "FIELD",
+ "name": "body",
+ "content": {
+ "type": "SYMBOL",
+ "name": "block"
+ }
+ }
+ ]
+ },
+ "circuit_declaration": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "export"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "pure"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "STRING",
+ "value": "circuit"
+ },
+ {
+ "type": "FIELD",
+ "name": "name",
+ "content": {
+ "type": "SYMBOL",
+ "name": "identifier"
+ }
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "generic_parameters"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "FIELD",
+ "name": "parameters",
+ "content": {
+ "type": "SYMBOL",
+ "name": "parameter_list"
+ }
+ },
+ {
+ "type": "STRING",
+ "value": ":"
+ },
+ {
+ "type": "FIELD",
+ "name": "type",
+ "content": {
+ "type": "SYMBOL",
+ "name": "type_expression"
+ }
+ },
+ {
+ "type": "FIELD",
+ "name": "body",
+ "content": {
+ "type": "SYMBOL",
+ "name": "block"
+ }
+ }
+ ]
+ },
+ "generic_parameters": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "<"
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "generic_parameter"
+ },
+ {
+ "type": "REPEAT",
+ "content": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": ","
+ },
+ {
+ "type": "SYMBOL",
+ "name": "generic_parameter"
+ }
+ ]
+ }
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": ","
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "STRING",
+ "value": ">"
+ }
+ ]
+ },
+ "generic_parameter": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "#"
+ },
+ {
+ "type": "BLANK"
+ }
+ ]
+ },
+ {
+ "type": "FIELD",
+ "name": "name",
+ "content": {
+ "type": "SYMBOL",
+ "name": "identifier"
+ }
+ }
+ ]
+ },
+ "generic_arguments": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "<"
+ },
+ {
+ "type": "REPEAT",
+ "content": {
+ "type": "SYMBOL",
+ "name": "_type_item"
+ }
+ },
+ {
+ "type": "STRING",
+ "value": ">"
+ }
+ ]
+ },
+ "type_expression": {
+ "type": "REPEAT1",
+ "content": {
+ "type": "SYMBOL",
+ "name": "_type_item"
+ }
+ },
+ "_type_item": {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "identifier"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "number"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "string"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "generic_arguments"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "_tuple_type"
+ },
+ {
+ "type": "STRING",
+ "value": ","
+ },
+ {
+ "type": "STRING",
+ "value": ":"
+ },
+ {
+ "type": "STRING",
+ "value": ".."
+ }
+ ]
+ },
+ "_tuple_type": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "["
+ },
+ {
+ "type": "REPEAT",
+ "content": {
+ "type": "SYMBOL",
+ "name": "_type_item"
+ }
+ },
+ {
+ "type": "STRING",
+ "value": "]"
+ }
+ ]
+ },
+ "block": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "{"
+ },
+ {
+ "type": "REPEAT",
+ "content": {
+ "type": "SYMBOL",
+ "name": "_opaque"
+ }
+ },
+ {
+ "type": "STRING",
+ "value": "}"
+ }
+ ]
+ },
+ "parameter_list": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "("
+ },
+ {
+ "type": "REPEAT",
+ "content": {
+ "type": "SYMBOL",
+ "name": "_opaque"
+ }
+ },
+ {
+ "type": "STRING",
+ "value": ")"
+ }
+ ]
+ },
+ "_paren_group": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "("
+ },
+ {
+ "type": "REPEAT",
+ "content": {
+ "type": "SYMBOL",
+ "name": "_opaque"
+ }
+ },
+ {
+ "type": "STRING",
+ "value": ")"
+ }
+ ]
+ },
+ "_bracket_group": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "["
+ },
+ {
+ "type": "REPEAT",
+ "content": {
+ "type": "SYMBOL",
+ "name": "_opaque"
+ }
+ },
+ {
+ "type": "STRING",
+ "value": "]"
+ }
+ ]
+ },
+ "_opaque": {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "block"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "_paren_group"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "_bracket_group"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "string"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "_opaque_token"
+ },
+ {
+ "type": "STRING",
+ "value": "/"
+ }
+ ]
+ },
+ "_opaque_token": {
+ "type": "TOKEN",
+ "content": {
+ "type": "PATTERN",
+ "value": "[^\\s(){}\\[\\]\"'\\/]+"
+ }
+ },
+ "identifier": {
+ "type": "PATTERN",
+ "value": "[A-Za-z_$][A-Za-z0-9_$]*"
+ },
+ "number": {
+ "type": "PATTERN",
+ "value": "[0-9]+"
+ },
+ "string": {
+ "type": "TOKEN",
+ "content": {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "\""
+ },
+ {
+ "type": "REPEAT",
+ "content": {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "PATTERN",
+ "value": "[^\"\\\\]"
+ },
+ {
+ "type": "PATTERN",
+ "value": "\\\\(.|\\r?\\n)"
+ }
+ ]
+ }
+ },
+ {
+ "type": "STRING",
+ "value": "\""
+ }
+ ]
+ },
+ {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "'"
+ },
+ {
+ "type": "REPEAT",
+ "content": {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "PATTERN",
+ "value": "[^'\\\\\\r\\n]"
+ },
+ {
+ "type": "PATTERN",
+ "value": "\\\\."
+ }
+ ]
+ }
+ },
+ {
+ "type": "STRING",
+ "value": "'"
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "doc_comment": {
+ "type": "TOKEN",
+ "content": {
+ "type": "PREC",
+ "value": 2,
+ "content": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "/**"
+ },
+ {
+ "type": "PATTERN",
+ "value": "[^*]*\\*+([^/*][^*]*\\*+)*"
+ },
+ {
+ "type": "STRING",
+ "value": "/"
+ }
+ ]
+ }
+ }
+ },
+ "block_comment": {
+ "type": "TOKEN",
+ "content": {
+ "type": "PREC",
+ "value": 1,
+ "content": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "/*"
+ },
+ {
+ "type": "PATTERN",
+ "value": "[^*]*\\*+([^/*][^*]*\\*+)*"
+ },
+ {
+ "type": "STRING",
+ "value": "/"
+ }
+ ]
+ }
+ }
+ },
+ "line_comment": {
+ "type": "TOKEN",
+ "content": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "//"
+ },
+ {
+ "type": "PATTERN",
+ "value": "[^\\r\\n]*"
+ }
+ ]
+ }
+ }
+ },
+ "extras": [
+ {
+ "type": "PATTERN",
+ "value": "\\s"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "doc_comment"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "block_comment"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "line_comment"
+ }
+ ],
+ "conflicts": [],
+ "precedences": [],
+ "externals": [],
+ "inline": [],
+ "supertypes": [],
+ "reserved": {}
+}
\ No newline at end of file
diff --git a/crates/tree-sitter-compact/src/node-types.json b/crates/tree-sitter-compact/src/node-types.json
new file mode 100644
index 0000000..9c16e7d
--- /dev/null
+++ b/crates/tree-sitter-compact/src/node-types.json
@@ -0,0 +1,913 @@
+[
+ {
+ "type": "block",
+ "named": true,
+ "fields": {},
+ "children": {
+ "multiple": true,
+ "required": false,
+ "types": [
+ {
+ "type": "block",
+ "named": true
+ },
+ {
+ "type": "string",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "circuit_declaration",
+ "named": true,
+ "fields": {
+ "body": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "block",
+ "named": true
+ }
+ ]
+ },
+ "name": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "identifier",
+ "named": true
+ }
+ ]
+ },
+ "parameters": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "parameter_list",
+ "named": true
+ }
+ ]
+ },
+ "type": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "type_expression",
+ "named": true
+ }
+ ]
+ }
+ },
+ "children": {
+ "multiple": false,
+ "required": false,
+ "types": [
+ {
+ "type": "generic_parameters",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "constructor_declaration",
+ "named": true,
+ "fields": {
+ "body": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "block",
+ "named": true
+ }
+ ]
+ },
+ "parameters": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "parameter_list",
+ "named": true
+ }
+ ]
+ }
+ }
+ },
+ {
+ "type": "contract_declaration",
+ "named": true,
+ "fields": {
+ "body": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "block",
+ "named": true
+ }
+ ]
+ },
+ "name": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "identifier",
+ "named": true
+ }
+ ]
+ }
+ }
+ },
+ {
+ "type": "enum_declaration",
+ "named": true,
+ "fields": {
+ "body": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "block",
+ "named": true
+ }
+ ]
+ },
+ "name": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "identifier",
+ "named": true
+ }
+ ]
+ }
+ }
+ },
+ {
+ "type": "export_declaration",
+ "named": true,
+ "fields": {},
+ "children": {
+ "multiple": true,
+ "required": false,
+ "types": [
+ {
+ "type": "identifier",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "generic_arguments",
+ "named": true,
+ "fields": {},
+ "children": {
+ "multiple": true,
+ "required": false,
+ "types": [
+ {
+ "type": "generic_arguments",
+ "named": true
+ },
+ {
+ "type": "identifier",
+ "named": true
+ },
+ {
+ "type": "number",
+ "named": true
+ },
+ {
+ "type": "string",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "generic_parameter",
+ "named": true,
+ "fields": {
+ "name": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "identifier",
+ "named": true
+ }
+ ]
+ }
+ }
+ },
+ {
+ "type": "generic_parameters",
+ "named": true,
+ "fields": {},
+ "children": {
+ "multiple": true,
+ "required": false,
+ "types": [
+ {
+ "type": "generic_parameter",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "implements_declaration",
+ "named": true,
+ "fields": {
+ "type": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "type_expression",
+ "named": true
+ }
+ ]
+ }
+ }
+ },
+ {
+ "type": "import_declaration",
+ "named": true,
+ "fields": {
+ "name": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "identifier",
+ "named": true
+ },
+ {
+ "type": "string",
+ "named": true
+ }
+ ]
+ }
+ },
+ "children": {
+ "multiple": true,
+ "required": false,
+ "types": [
+ {
+ "type": "generic_arguments",
+ "named": true
+ },
+ {
+ "type": "import_prefix",
+ "named": true
+ },
+ {
+ "type": "import_selection",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "import_element",
+ "named": true,
+ "fields": {
+ "alias": {
+ "multiple": false,
+ "required": false,
+ "types": [
+ {
+ "type": "identifier",
+ "named": true
+ }
+ ]
+ },
+ "name": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "identifier",
+ "named": true
+ }
+ ]
+ }
+ }
+ },
+ {
+ "type": "import_prefix",
+ "named": true,
+ "fields": {
+ "prefix": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "identifier",
+ "named": true
+ }
+ ]
+ }
+ }
+ },
+ {
+ "type": "import_selection",
+ "named": true,
+ "fields": {},
+ "children": {
+ "multiple": true,
+ "required": false,
+ "types": [
+ {
+ "type": "import_element",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "include_declaration",
+ "named": true,
+ "fields": {
+ "path": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "string",
+ "named": true
+ }
+ ]
+ }
+ }
+ },
+ {
+ "type": "ledger_declaration",
+ "named": true,
+ "fields": {
+ "name": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "identifier",
+ "named": true
+ }
+ ]
+ },
+ "type": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "type_expression",
+ "named": true
+ }
+ ]
+ }
+ }
+ },
+ {
+ "type": "module_body",
+ "named": true,
+ "fields": {},
+ "children": {
+ "multiple": true,
+ "required": false,
+ "types": [
+ {
+ "type": "circuit_declaration",
+ "named": true
+ },
+ {
+ "type": "constructor_declaration",
+ "named": true
+ },
+ {
+ "type": "contract_declaration",
+ "named": true
+ },
+ {
+ "type": "enum_declaration",
+ "named": true
+ },
+ {
+ "type": "export_declaration",
+ "named": true
+ },
+ {
+ "type": "implements_declaration",
+ "named": true
+ },
+ {
+ "type": "import_declaration",
+ "named": true
+ },
+ {
+ "type": "include_declaration",
+ "named": true
+ },
+ {
+ "type": "ledger_declaration",
+ "named": true
+ },
+ {
+ "type": "module_declaration",
+ "named": true
+ },
+ {
+ "type": "pragma_declaration",
+ "named": true
+ },
+ {
+ "type": "struct_declaration",
+ "named": true
+ },
+ {
+ "type": "type_declaration",
+ "named": true
+ },
+ {
+ "type": "witness_declaration",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "module_declaration",
+ "named": true,
+ "fields": {
+ "body": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "module_body",
+ "named": true
+ }
+ ]
+ },
+ "name": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "identifier",
+ "named": true
+ }
+ ]
+ }
+ },
+ "children": {
+ "multiple": false,
+ "required": false,
+ "types": [
+ {
+ "type": "generic_parameters",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "parameter_list",
+ "named": true,
+ "fields": {},
+ "children": {
+ "multiple": true,
+ "required": false,
+ "types": [
+ {
+ "type": "block",
+ "named": true
+ },
+ {
+ "type": "string",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "pragma_declaration",
+ "named": true,
+ "fields": {
+ "name": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "identifier",
+ "named": true
+ }
+ ]
+ }
+ },
+ "children": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "version_constraint",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "source_file",
+ "named": true,
+ "root": true,
+ "fields": {},
+ "children": {
+ "multiple": true,
+ "required": false,
+ "types": [
+ {
+ "type": "circuit_declaration",
+ "named": true
+ },
+ {
+ "type": "constructor_declaration",
+ "named": true
+ },
+ {
+ "type": "contract_declaration",
+ "named": true
+ },
+ {
+ "type": "enum_declaration",
+ "named": true
+ },
+ {
+ "type": "export_declaration",
+ "named": true
+ },
+ {
+ "type": "implements_declaration",
+ "named": true
+ },
+ {
+ "type": "import_declaration",
+ "named": true
+ },
+ {
+ "type": "include_declaration",
+ "named": true
+ },
+ {
+ "type": "ledger_declaration",
+ "named": true
+ },
+ {
+ "type": "module_declaration",
+ "named": true
+ },
+ {
+ "type": "pragma_declaration",
+ "named": true
+ },
+ {
+ "type": "struct_declaration",
+ "named": true
+ },
+ {
+ "type": "type_declaration",
+ "named": true
+ },
+ {
+ "type": "witness_declaration",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "struct_declaration",
+ "named": true,
+ "fields": {
+ "body": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "block",
+ "named": true
+ }
+ ]
+ },
+ "name": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "identifier",
+ "named": true
+ }
+ ]
+ }
+ },
+ "children": {
+ "multiple": false,
+ "required": false,
+ "types": [
+ {
+ "type": "generic_parameters",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "type_declaration",
+ "named": true,
+ "fields": {
+ "name": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "identifier",
+ "named": true
+ }
+ ]
+ },
+ "value": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "type_expression",
+ "named": true
+ }
+ ]
+ }
+ },
+ "children": {
+ "multiple": false,
+ "required": false,
+ "types": [
+ {
+ "type": "generic_parameters",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "type_expression",
+ "named": true,
+ "fields": {},
+ "children": {
+ "multiple": true,
+ "required": false,
+ "types": [
+ {
+ "type": "generic_arguments",
+ "named": true
+ },
+ {
+ "type": "identifier",
+ "named": true
+ },
+ {
+ "type": "number",
+ "named": true
+ },
+ {
+ "type": "string",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "witness_declaration",
+ "named": true,
+ "fields": {
+ "name": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "identifier",
+ "named": true
+ }
+ ]
+ },
+ "parameters": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "parameter_list",
+ "named": true
+ }
+ ]
+ },
+ "type": {
+ "multiple": false,
+ "required": true,
+ "types": [
+ {
+ "type": "type_expression",
+ "named": true
+ }
+ ]
+ }
+ },
+ "children": {
+ "multiple": false,
+ "required": false,
+ "types": [
+ {
+ "type": "generic_parameters",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "#",
+ "named": false
+ },
+ {
+ "type": "(",
+ "named": false
+ },
+ {
+ "type": ")",
+ "named": false
+ },
+ {
+ "type": ",",
+ "named": false
+ },
+ {
+ "type": "..",
+ "named": false
+ },
+ {
+ "type": "/",
+ "named": false
+ },
+ {
+ "type": ":",
+ "named": false
+ },
+ {
+ "type": ";",
+ "named": false
+ },
+ {
+ "type": "<",
+ "named": false
+ },
+ {
+ "type": "=",
+ "named": false
+ },
+ {
+ "type": ">",
+ "named": false
+ },
+ {
+ "type": "[",
+ "named": false
+ },
+ {
+ "type": "]",
+ "named": false
+ },
+ {
+ "type": "as",
+ "named": false
+ },
+ {
+ "type": "block_comment",
+ "named": true,
+ "extra": true
+ },
+ {
+ "type": "circuit",
+ "named": false
+ },
+ {
+ "type": "constructor",
+ "named": false
+ },
+ {
+ "type": "contract",
+ "named": false
+ },
+ {
+ "type": "doc_comment",
+ "named": true,
+ "extra": true
+ },
+ {
+ "type": "enum",
+ "named": false
+ },
+ {
+ "type": "export",
+ "named": false
+ },
+ {
+ "type": "from",
+ "named": false
+ },
+ {
+ "type": "identifier",
+ "named": true
+ },
+ {
+ "type": "implements",
+ "named": false
+ },
+ {
+ "type": "import",
+ "named": false
+ },
+ {
+ "type": "include",
+ "named": false
+ },
+ {
+ "type": "ledger",
+ "named": false
+ },
+ {
+ "type": "line_comment",
+ "named": true,
+ "extra": true
+ },
+ {
+ "type": "module",
+ "named": false
+ },
+ {
+ "type": "new",
+ "named": false
+ },
+ {
+ "type": "number",
+ "named": true
+ },
+ {
+ "type": "pragma",
+ "named": false
+ },
+ {
+ "type": "prefix",
+ "named": false
+ },
+ {
+ "type": "pure",
+ "named": false
+ },
+ {
+ "type": "sealed",
+ "named": false
+ },
+ {
+ "type": "string",
+ "named": true
+ },
+ {
+ "type": "struct",
+ "named": false
+ },
+ {
+ "type": "type",
+ "named": false
+ },
+ {
+ "type": "version_constraint",
+ "named": true
+ },
+ {
+ "type": "witness",
+ "named": false
+ },
+ {
+ "type": "{",
+ "named": false
+ },
+ {
+ "type": "}",
+ "named": false
+ }
+]
\ No newline at end of file
diff --git a/crates/tree-sitter-compact/src/parser.c b/crates/tree-sitter-compact/src/parser.c
new file mode 100644
index 0000000..3166d21
--- /dev/null
+++ b/crates/tree-sitter-compact/src/parser.c
@@ -0,0 +1,7042 @@
+/* Automatically @generated by tree-sitter v0.25.8 */
+
+#include "tree_sitter/parser.h"
+
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+#endif
+
+#define LANGUAGE_VERSION 15
+#define STATE_COUNT 265
+#define LARGE_STATE_COUNT 2
+#define SYMBOL_COUNT 81
+#define ALIAS_COUNT 0
+#define TOKEN_COUNT 44
+#define EXTERNAL_TOKEN_COUNT 0
+#define FIELD_COUNT 8
+#define MAX_ALIAS_SEQUENCE_LENGTH 9
+#define MAX_RESERVED_WORD_SET_SIZE 0
+#define PRODUCTION_ID_COUNT 32
+#define SUPERTYPE_COUNT 0
+
+enum ts_symbol_identifiers {
+ sym_identifier = 1,
+ anon_sym_pragma = 2,
+ anon_sym_SEMI = 3,
+ sym_version_constraint = 4,
+ anon_sym_include = 5,
+ anon_sym_import = 6,
+ anon_sym_LBRACE = 7,
+ anon_sym_COMMA = 8,
+ anon_sym_RBRACE = 9,
+ anon_sym_from = 10,
+ anon_sym_as = 11,
+ anon_sym_prefix = 12,
+ anon_sym_export = 13,
+ anon_sym_module = 14,
+ anon_sym_struct = 15,
+ anon_sym_enum = 16,
+ anon_sym_contract = 17,
+ anon_sym_implements = 18,
+ anon_sym_new = 19,
+ anon_sym_type = 20,
+ anon_sym_EQ = 21,
+ anon_sym_sealed = 22,
+ anon_sym_ledger = 23,
+ anon_sym_COLON = 24,
+ anon_sym_witness = 25,
+ anon_sym_constructor = 26,
+ anon_sym_pure = 27,
+ anon_sym_circuit = 28,
+ anon_sym_LT = 29,
+ anon_sym_GT = 30,
+ anon_sym_POUND = 31,
+ anon_sym_DOT_DOT = 32,
+ anon_sym_LBRACK = 33,
+ anon_sym_RBRACK = 34,
+ anon_sym_LPAREN = 35,
+ anon_sym_RPAREN = 36,
+ anon_sym_SLASH = 37,
+ sym__opaque_token = 38,
+ sym_number = 39,
+ sym_string = 40,
+ sym_doc_comment = 41,
+ sym_block_comment = 42,
+ sym_line_comment = 43,
+ sym_source_file = 44,
+ sym__declaration = 45,
+ sym_pragma_declaration = 46,
+ sym_include_declaration = 47,
+ sym_import_declaration = 48,
+ sym_import_selection = 49,
+ sym_import_element = 50,
+ sym_import_prefix = 51,
+ sym_export_declaration = 52,
+ sym_module_declaration = 53,
+ sym_module_body = 54,
+ sym_struct_declaration = 55,
+ sym_enum_declaration = 56,
+ sym_implements_declaration = 57,
+ sym_contract_declaration = 58,
+ sym_type_declaration = 59,
+ sym_ledger_declaration = 60,
+ sym_witness_declaration = 61,
+ sym_constructor_declaration = 62,
+ sym_circuit_declaration = 63,
+ sym_generic_parameters = 64,
+ sym_generic_parameter = 65,
+ sym_generic_arguments = 66,
+ sym_type_expression = 67,
+ sym__type_item = 68,
+ sym__tuple_type = 69,
+ sym_block = 70,
+ sym_parameter_list = 71,
+ sym__paren_group = 72,
+ sym__bracket_group = 73,
+ sym__opaque = 74,
+ aux_sym_source_file_repeat1 = 75,
+ aux_sym_import_selection_repeat1 = 76,
+ aux_sym_export_declaration_repeat1 = 77,
+ aux_sym_generic_parameters_repeat1 = 78,
+ aux_sym_generic_arguments_repeat1 = 79,
+ aux_sym_block_repeat1 = 80,
+};
+
+static const char * const ts_symbol_names[] = {
+ [ts_builtin_sym_end] = "end",
+ [sym_identifier] = "identifier",
+ [anon_sym_pragma] = "pragma",
+ [anon_sym_SEMI] = ";",
+ [sym_version_constraint] = "version_constraint",
+ [anon_sym_include] = "include",
+ [anon_sym_import] = "import",
+ [anon_sym_LBRACE] = "{",
+ [anon_sym_COMMA] = ",",
+ [anon_sym_RBRACE] = "}",
+ [anon_sym_from] = "from",
+ [anon_sym_as] = "as",
+ [anon_sym_prefix] = "prefix",
+ [anon_sym_export] = "export",
+ [anon_sym_module] = "module",
+ [anon_sym_struct] = "struct",
+ [anon_sym_enum] = "enum",
+ [anon_sym_contract] = "contract",
+ [anon_sym_implements] = "implements",
+ [anon_sym_new] = "new",
+ [anon_sym_type] = "type",
+ [anon_sym_EQ] = "=",
+ [anon_sym_sealed] = "sealed",
+ [anon_sym_ledger] = "ledger",
+ [anon_sym_COLON] = ":",
+ [anon_sym_witness] = "witness",
+ [anon_sym_constructor] = "constructor",
+ [anon_sym_pure] = "pure",
+ [anon_sym_circuit] = "circuit",
+ [anon_sym_LT] = "<",
+ [anon_sym_GT] = ">",
+ [anon_sym_POUND] = "#",
+ [anon_sym_DOT_DOT] = "..",
+ [anon_sym_LBRACK] = "[",
+ [anon_sym_RBRACK] = "]",
+ [anon_sym_LPAREN] = "(",
+ [anon_sym_RPAREN] = ")",
+ [anon_sym_SLASH] = "/",
+ [sym__opaque_token] = "_opaque_token",
+ [sym_number] = "number",
+ [sym_string] = "string",
+ [sym_doc_comment] = "doc_comment",
+ [sym_block_comment] = "block_comment",
+ [sym_line_comment] = "line_comment",
+ [sym_source_file] = "source_file",
+ [sym__declaration] = "_declaration",
+ [sym_pragma_declaration] = "pragma_declaration",
+ [sym_include_declaration] = "include_declaration",
+ [sym_import_declaration] = "import_declaration",
+ [sym_import_selection] = "import_selection",
+ [sym_import_element] = "import_element",
+ [sym_import_prefix] = "import_prefix",
+ [sym_export_declaration] = "export_declaration",
+ [sym_module_declaration] = "module_declaration",
+ [sym_module_body] = "module_body",
+ [sym_struct_declaration] = "struct_declaration",
+ [sym_enum_declaration] = "enum_declaration",
+ [sym_implements_declaration] = "implements_declaration",
+ [sym_contract_declaration] = "contract_declaration",
+ [sym_type_declaration] = "type_declaration",
+ [sym_ledger_declaration] = "ledger_declaration",
+ [sym_witness_declaration] = "witness_declaration",
+ [sym_constructor_declaration] = "constructor_declaration",
+ [sym_circuit_declaration] = "circuit_declaration",
+ [sym_generic_parameters] = "generic_parameters",
+ [sym_generic_parameter] = "generic_parameter",
+ [sym_generic_arguments] = "generic_arguments",
+ [sym_type_expression] = "type_expression",
+ [sym__type_item] = "_type_item",
+ [sym__tuple_type] = "_tuple_type",
+ [sym_block] = "block",
+ [sym_parameter_list] = "parameter_list",
+ [sym__paren_group] = "_paren_group",
+ [sym__bracket_group] = "_bracket_group",
+ [sym__opaque] = "_opaque",
+ [aux_sym_source_file_repeat1] = "source_file_repeat1",
+ [aux_sym_import_selection_repeat1] = "import_selection_repeat1",
+ [aux_sym_export_declaration_repeat1] = "export_declaration_repeat1",
+ [aux_sym_generic_parameters_repeat1] = "generic_parameters_repeat1",
+ [aux_sym_generic_arguments_repeat1] = "generic_arguments_repeat1",
+ [aux_sym_block_repeat1] = "block_repeat1",
+};
+
+static const TSSymbol ts_symbol_map[] = {
+ [ts_builtin_sym_end] = ts_builtin_sym_end,
+ [sym_identifier] = sym_identifier,
+ [anon_sym_pragma] = anon_sym_pragma,
+ [anon_sym_SEMI] = anon_sym_SEMI,
+ [sym_version_constraint] = sym_version_constraint,
+ [anon_sym_include] = anon_sym_include,
+ [anon_sym_import] = anon_sym_import,
+ [anon_sym_LBRACE] = anon_sym_LBRACE,
+ [anon_sym_COMMA] = anon_sym_COMMA,
+ [anon_sym_RBRACE] = anon_sym_RBRACE,
+ [anon_sym_from] = anon_sym_from,
+ [anon_sym_as] = anon_sym_as,
+ [anon_sym_prefix] = anon_sym_prefix,
+ [anon_sym_export] = anon_sym_export,
+ [anon_sym_module] = anon_sym_module,
+ [anon_sym_struct] = anon_sym_struct,
+ [anon_sym_enum] = anon_sym_enum,
+ [anon_sym_contract] = anon_sym_contract,
+ [anon_sym_implements] = anon_sym_implements,
+ [anon_sym_new] = anon_sym_new,
+ [anon_sym_type] = anon_sym_type,
+ [anon_sym_EQ] = anon_sym_EQ,
+ [anon_sym_sealed] = anon_sym_sealed,
+ [anon_sym_ledger] = anon_sym_ledger,
+ [anon_sym_COLON] = anon_sym_COLON,
+ [anon_sym_witness] = anon_sym_witness,
+ [anon_sym_constructor] = anon_sym_constructor,
+ [anon_sym_pure] = anon_sym_pure,
+ [anon_sym_circuit] = anon_sym_circuit,
+ [anon_sym_LT] = anon_sym_LT,
+ [anon_sym_GT] = anon_sym_GT,
+ [anon_sym_POUND] = anon_sym_POUND,
+ [anon_sym_DOT_DOT] = anon_sym_DOT_DOT,
+ [anon_sym_LBRACK] = anon_sym_LBRACK,
+ [anon_sym_RBRACK] = anon_sym_RBRACK,
+ [anon_sym_LPAREN] = anon_sym_LPAREN,
+ [anon_sym_RPAREN] = anon_sym_RPAREN,
+ [anon_sym_SLASH] = anon_sym_SLASH,
+ [sym__opaque_token] = sym__opaque_token,
+ [sym_number] = sym_number,
+ [sym_string] = sym_string,
+ [sym_doc_comment] = sym_doc_comment,
+ [sym_block_comment] = sym_block_comment,
+ [sym_line_comment] = sym_line_comment,
+ [sym_source_file] = sym_source_file,
+ [sym__declaration] = sym__declaration,
+ [sym_pragma_declaration] = sym_pragma_declaration,
+ [sym_include_declaration] = sym_include_declaration,
+ [sym_import_declaration] = sym_import_declaration,
+ [sym_import_selection] = sym_import_selection,
+ [sym_import_element] = sym_import_element,
+ [sym_import_prefix] = sym_import_prefix,
+ [sym_export_declaration] = sym_export_declaration,
+ [sym_module_declaration] = sym_module_declaration,
+ [sym_module_body] = sym_module_body,
+ [sym_struct_declaration] = sym_struct_declaration,
+ [sym_enum_declaration] = sym_enum_declaration,
+ [sym_implements_declaration] = sym_implements_declaration,
+ [sym_contract_declaration] = sym_contract_declaration,
+ [sym_type_declaration] = sym_type_declaration,
+ [sym_ledger_declaration] = sym_ledger_declaration,
+ [sym_witness_declaration] = sym_witness_declaration,
+ [sym_constructor_declaration] = sym_constructor_declaration,
+ [sym_circuit_declaration] = sym_circuit_declaration,
+ [sym_generic_parameters] = sym_generic_parameters,
+ [sym_generic_parameter] = sym_generic_parameter,
+ [sym_generic_arguments] = sym_generic_arguments,
+ [sym_type_expression] = sym_type_expression,
+ [sym__type_item] = sym__type_item,
+ [sym__tuple_type] = sym__tuple_type,
+ [sym_block] = sym_block,
+ [sym_parameter_list] = sym_parameter_list,
+ [sym__paren_group] = sym__paren_group,
+ [sym__bracket_group] = sym__bracket_group,
+ [sym__opaque] = sym__opaque,
+ [aux_sym_source_file_repeat1] = aux_sym_source_file_repeat1,
+ [aux_sym_import_selection_repeat1] = aux_sym_import_selection_repeat1,
+ [aux_sym_export_declaration_repeat1] = aux_sym_export_declaration_repeat1,
+ [aux_sym_generic_parameters_repeat1] = aux_sym_generic_parameters_repeat1,
+ [aux_sym_generic_arguments_repeat1] = aux_sym_generic_arguments_repeat1,
+ [aux_sym_block_repeat1] = aux_sym_block_repeat1,
+};
+
+static const TSSymbolMetadata ts_symbol_metadata[] = {
+ [ts_builtin_sym_end] = {
+ .visible = false,
+ .named = true,
+ },
+ [sym_identifier] = {
+ .visible = true,
+ .named = true,
+ },
+ [anon_sym_pragma] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_SEMI] = {
+ .visible = true,
+ .named = false,
+ },
+ [sym_version_constraint] = {
+ .visible = true,
+ .named = true,
+ },
+ [anon_sym_include] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_import] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_LBRACE] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_COMMA] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_RBRACE] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_from] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_as] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_prefix] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_export] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_module] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_struct] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_enum] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_contract] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_implements] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_new] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_type] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_EQ] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_sealed] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_ledger] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_COLON] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_witness] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_constructor] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_pure] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_circuit] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_LT] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_GT] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_POUND] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_DOT_DOT] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_LBRACK] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_RBRACK] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_LPAREN] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_RPAREN] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_SLASH] = {
+ .visible = true,
+ .named = false,
+ },
+ [sym__opaque_token] = {
+ .visible = false,
+ .named = true,
+ },
+ [sym_number] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_string] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_doc_comment] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_block_comment] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_line_comment] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_source_file] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym__declaration] = {
+ .visible = false,
+ .named = true,
+ },
+ [sym_pragma_declaration] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_include_declaration] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_import_declaration] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_import_selection] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_import_element] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_import_prefix] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_export_declaration] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_module_declaration] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_module_body] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_struct_declaration] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_enum_declaration] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_implements_declaration] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_contract_declaration] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_type_declaration] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_ledger_declaration] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_witness_declaration] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_constructor_declaration] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_circuit_declaration] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_generic_parameters] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_generic_parameter] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_generic_arguments] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_type_expression] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym__type_item] = {
+ .visible = false,
+ .named = true,
+ },
+ [sym__tuple_type] = {
+ .visible = false,
+ .named = true,
+ },
+ [sym_block] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_parameter_list] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym__paren_group] = {
+ .visible = false,
+ .named = true,
+ },
+ [sym__bracket_group] = {
+ .visible = false,
+ .named = true,
+ },
+ [sym__opaque] = {
+ .visible = false,
+ .named = true,
+ },
+ [aux_sym_source_file_repeat1] = {
+ .visible = false,
+ .named = false,
+ },
+ [aux_sym_import_selection_repeat1] = {
+ .visible = false,
+ .named = false,
+ },
+ [aux_sym_export_declaration_repeat1] = {
+ .visible = false,
+ .named = false,
+ },
+ [aux_sym_generic_parameters_repeat1] = {
+ .visible = false,
+ .named = false,
+ },
+ [aux_sym_generic_arguments_repeat1] = {
+ .visible = false,
+ .named = false,
+ },
+ [aux_sym_block_repeat1] = {
+ .visible = false,
+ .named = false,
+ },
+};
+
+enum ts_field_identifiers {
+ field_alias = 1,
+ field_body = 2,
+ field_name = 3,
+ field_parameters = 4,
+ field_path = 5,
+ field_prefix = 6,
+ field_type = 7,
+ field_value = 8,
+};
+
+static const char * const ts_field_names[] = {
+ [0] = NULL,
+ [field_alias] = "alias",
+ [field_body] = "body",
+ [field_name] = "name",
+ [field_parameters] = "parameters",
+ [field_path] = "path",
+ [field_prefix] = "prefix",
+ [field_type] = "type",
+ [field_value] = "value",
+};
+
+static const TSMapSlice ts_field_map_slices[PRODUCTION_ID_COUNT] = {
+ [1] = {.index = 0, .length = 1},
+ [2] = {.index = 1, .length = 1},
+ [3] = {.index = 2, .length = 1},
+ [4] = {.index = 3, .length = 2},
+ [5] = {.index = 5, .length = 2},
+ [6] = {.index = 7, .length = 1},
+ [7] = {.index = 8, .length = 1},
+ [8] = {.index = 9, .length = 2},
+ [9] = {.index = 11, .length = 2},
+ [10] = {.index = 13, .length = 1},
+ [11] = {.index = 14, .length = 2},
+ [12] = {.index = 16, .length = 2},
+ [13] = {.index = 18, .length = 2},
+ [14] = {.index = 20, .length = 2},
+ [15] = {.index = 22, .length = 2},
+ [16] = {.index = 24, .length = 2},
+ [17] = {.index = 26, .length = 2},
+ [18] = {.index = 28, .length = 3},
+ [19] = {.index = 31, .length = 4},
+ [20] = {.index = 35, .length = 2},
+ [21] = {.index = 37, .length = 2},
+ [22] = {.index = 39, .length = 2},
+ [23] = {.index = 41, .length = 3},
+ [24] = {.index = 44, .length = 4},
+ [25] = {.index = 48, .length = 3},
+ [26] = {.index = 51, .length = 4},
+ [27] = {.index = 55, .length = 2},
+ [28] = {.index = 57, .length = 3},
+ [29] = {.index = 60, .length = 4},
+ [30] = {.index = 64, .length = 4},
+ [31] = {.index = 68, .length = 4},
+};
+
+static const TSFieldMapEntry ts_field_map_entries[] = {
+ [0] =
+ {field_path, 1},
+ [1] =
+ {field_name, 0},
+ [2] =
+ {field_name, 1},
+ [3] =
+ {field_body, 2},
+ {field_name, 1},
+ [5] =
+ {field_body, 2},
+ {field_parameters, 1},
+ [7] =
+ {field_prefix, 1},
+ [8] =
+ {field_name, 2},
+ [9] =
+ {field_body, 3},
+ {field_name, 2},
+ [11] =
+ {field_body, 3},
+ {field_name, 1},
+ [13] =
+ {field_type, 2},
+ [14] =
+ {field_alias, 2},
+ {field_name, 0},
+ [16] =
+ {field_body, 4},
+ {field_name, 2},
+ [18] =
+ {field_name, 1},
+ {field_value, 3},
+ [20] =
+ {field_name, 1},
+ {field_type, 3},
+ [22] =
+ {field_name, 2},
+ {field_value, 4},
+ [24] =
+ {field_name, 2},
+ {field_type, 4},
+ [26] =
+ {field_name, 1},
+ {field_value, 4},
+ [28] =
+ {field_name, 1},
+ {field_parameters, 2},
+ {field_type, 4},
+ [31] =
+ {field_body, 5},
+ {field_name, 1},
+ {field_parameters, 2},
+ {field_type, 4},
+ [35] =
+ {field_name, 3},
+ {field_value, 5},
+ [37] =
+ {field_name, 2},
+ {field_value, 5},
+ [39] =
+ {field_name, 3},
+ {field_type, 5},
+ [41] =
+ {field_name, 2},
+ {field_parameters, 3},
+ {field_type, 5},
+ [44] =
+ {field_body, 6},
+ {field_name, 2},
+ {field_parameters, 3},
+ {field_type, 5},
+ [48] =
+ {field_name, 1},
+ {field_parameters, 3},
+ {field_type, 5},
+ [51] =
+ {field_body, 6},
+ {field_name, 1},
+ {field_parameters, 3},
+ {field_type, 5},
+ [55] =
+ {field_name, 3},
+ {field_value, 6},
+ [57] =
+ {field_name, 2},
+ {field_parameters, 4},
+ {field_type, 6},
+ [60] =
+ {field_body, 7},
+ {field_name, 3},
+ {field_parameters, 4},
+ {field_type, 6},
+ [64] =
+ {field_body, 7},
+ {field_name, 2},
+ {field_parameters, 4},
+ {field_type, 6},
+ [68] =
+ {field_body, 8},
+ {field_name, 3},
+ {field_parameters, 5},
+ {field_type, 7},
+};
+
+static const TSSymbol ts_alias_sequences[PRODUCTION_ID_COUNT][MAX_ALIAS_SEQUENCE_LENGTH] = {
+ [0] = {0},
+};
+
+static const uint16_t ts_non_terminal_alias_map[] = {
+ 0,
+};
+
+static const TSStateId ts_primary_state_ids[STATE_COUNT] = {
+ [0] = 0,
+ [1] = 1,
+ [2] = 2,
+ [3] = 3,
+ [4] = 4,
+ [5] = 5,
+ [6] = 6,
+ [7] = 7,
+ [8] = 8,
+ [9] = 9,
+ [10] = 10,
+ [11] = 11,
+ [12] = 12,
+ [13] = 13,
+ [14] = 14,
+ [15] = 15,
+ [16] = 16,
+ [17] = 17,
+ [18] = 18,
+ [19] = 19,
+ [20] = 20,
+ [21] = 21,
+ [22] = 22,
+ [23] = 23,
+ [24] = 24,
+ [25] = 25,
+ [26] = 26,
+ [27] = 27,
+ [28] = 28,
+ [29] = 29,
+ [30] = 30,
+ [31] = 31,
+ [32] = 32,
+ [33] = 33,
+ [34] = 34,
+ [35] = 35,
+ [36] = 36,
+ [37] = 37,
+ [38] = 38,
+ [39] = 39,
+ [40] = 40,
+ [41] = 41,
+ [42] = 42,
+ [43] = 43,
+ [44] = 44,
+ [45] = 45,
+ [46] = 46,
+ [47] = 47,
+ [48] = 48,
+ [49] = 49,
+ [50] = 50,
+ [51] = 51,
+ [52] = 52,
+ [53] = 53,
+ [54] = 54,
+ [55] = 55,
+ [56] = 56,
+ [57] = 57,
+ [58] = 58,
+ [59] = 59,
+ [60] = 60,
+ [61] = 61,
+ [62] = 62,
+ [63] = 63,
+ [64] = 64,
+ [65] = 65,
+ [66] = 66,
+ [67] = 67,
+ [68] = 68,
+ [69] = 69,
+ [70] = 70,
+ [71] = 71,
+ [72] = 72,
+ [73] = 73,
+ [74] = 74,
+ [75] = 75,
+ [76] = 76,
+ [77] = 77,
+ [78] = 78,
+ [79] = 79,
+ [80] = 80,
+ [81] = 81,
+ [82] = 82,
+ [83] = 83,
+ [84] = 84,
+ [85] = 85,
+ [86] = 86,
+ [87] = 87,
+ [88] = 88,
+ [89] = 89,
+ [90] = 90,
+ [91] = 91,
+ [92] = 92,
+ [93] = 93,
+ [94] = 94,
+ [95] = 95,
+ [96] = 96,
+ [97] = 97,
+ [98] = 96,
+ [99] = 99,
+ [100] = 100,
+ [101] = 101,
+ [102] = 102,
+ [103] = 103,
+ [104] = 104,
+ [105] = 105,
+ [106] = 106,
+ [107] = 107,
+ [108] = 100,
+ [109] = 109,
+ [110] = 110,
+ [111] = 111,
+ [112] = 112,
+ [113] = 15,
+ [114] = 18,
+ [115] = 115,
+ [116] = 116,
+ [117] = 117,
+ [118] = 118,
+ [119] = 119,
+ [120] = 120,
+ [121] = 121,
+ [122] = 122,
+ [123] = 123,
+ [124] = 124,
+ [125] = 125,
+ [126] = 126,
+ [127] = 127,
+ [128] = 128,
+ [129] = 129,
+ [130] = 130,
+ [131] = 131,
+ [132] = 132,
+ [133] = 133,
+ [134] = 134,
+ [135] = 135,
+ [136] = 136,
+ [137] = 137,
+ [138] = 138,
+ [139] = 139,
+ [140] = 140,
+ [141] = 141,
+ [142] = 142,
+ [143] = 143,
+ [144] = 144,
+ [145] = 145,
+ [146] = 146,
+ [147] = 147,
+ [148] = 148,
+ [149] = 149,
+ [150] = 150,
+ [151] = 151,
+ [152] = 152,
+ [153] = 153,
+ [154] = 154,
+ [155] = 155,
+ [156] = 156,
+ [157] = 157,
+ [158] = 158,
+ [159] = 159,
+ [160] = 160,
+ [161] = 161,
+ [162] = 162,
+ [163] = 163,
+ [164] = 164,
+ [165] = 165,
+ [166] = 166,
+ [167] = 167,
+ [168] = 168,
+ [169] = 169,
+ [170] = 170,
+ [171] = 171,
+ [172] = 172,
+ [173] = 173,
+ [174] = 174,
+ [175] = 175,
+ [176] = 176,
+ [177] = 177,
+ [178] = 178,
+ [179] = 179,
+ [180] = 180,
+ [181] = 181,
+ [182] = 182,
+ [183] = 183,
+ [184] = 184,
+ [185] = 185,
+ [186] = 186,
+ [187] = 187,
+ [188] = 188,
+ [189] = 189,
+ [190] = 190,
+ [191] = 191,
+ [192] = 192,
+ [193] = 193,
+ [194] = 194,
+ [195] = 195,
+ [196] = 196,
+ [197] = 197,
+ [198] = 198,
+ [199] = 199,
+ [200] = 200,
+ [201] = 201,
+ [202] = 202,
+ [203] = 203,
+ [204] = 204,
+ [205] = 205,
+ [206] = 206,
+ [207] = 207,
+ [208] = 208,
+ [209] = 209,
+ [210] = 210,
+ [211] = 211,
+ [212] = 212,
+ [213] = 213,
+ [214] = 214,
+ [215] = 215,
+ [216] = 216,
+ [217] = 217,
+ [218] = 218,
+ [219] = 219,
+ [220] = 220,
+ [221] = 221,
+ [222] = 222,
+ [223] = 223,
+ [224] = 224,
+ [225] = 225,
+ [226] = 226,
+ [227] = 227,
+ [228] = 228,
+ [229] = 229,
+ [230] = 230,
+ [231] = 231,
+ [232] = 232,
+ [233] = 233,
+ [234] = 234,
+ [235] = 235,
+ [236] = 236,
+ [237] = 237,
+ [238] = 238,
+ [239] = 239,
+ [240] = 240,
+ [241] = 241,
+ [242] = 242,
+ [243] = 243,
+ [244] = 244,
+ [245] = 245,
+ [246] = 246,
+ [247] = 247,
+ [248] = 248,
+ [249] = 249,
+ [250] = 250,
+ [251] = 251,
+ [252] = 252,
+ [253] = 253,
+ [254] = 254,
+ [255] = 255,
+ [256] = 256,
+ [257] = 257,
+ [258] = 258,
+ [259] = 259,
+ [260] = 260,
+ [261] = 261,
+ [262] = 262,
+ [263] = 263,
+ [264] = 264,
+};
+
+static const TSCharacterRange sym__opaque_token_character_set_1[] = {
+ {0, 0x08}, {0x0e, 0x1f}, {'!', '!'}, {'#', '&'}, {'*', '.'}, {'0', 'Z'}, {'\\', '\\'}, {'^', 'z'},
+ {'|', '|'}, {'~', 0x10ffff},
+};
+
+static bool ts_lex(TSLexer *lexer, TSStateId state) {
+ START_LEXER();
+ eof = lexer->eof(lexer);
+ switch (state) {
+ case 0:
+ if (eof) ADVANCE(14);
+ ADVANCE_MAP(
+ '"', 3,
+ '#', 33,
+ '\'', 4,
+ '(', 37,
+ ')', 38,
+ ',', 27,
+ '.', 11,
+ '/', 39,
+ ':', 30,
+ ';', 15,
+ '<', 31,
+ '=', 29,
+ '>', 32,
+ '[', 35,
+ ']', 36,
+ '{', 26,
+ '}', 28,
+ );
+ if (('\t' <= lookahead && lookahead <= '\r') ||
+ lookahead == ' ') SKIP(0);
+ if (('0' <= lookahead && lookahead <= '9')) ADVANCE(42);
+ if (lookahead == '$' ||
+ ('A' <= lookahead && lookahead <= 'Z') ||
+ lookahead == '_' ||
+ ('a' <= lookahead && lookahead <= 'z')) ADVANCE(41);
+ END_STATE();
+ case 1:
+ if (lookahead == '\r') ADVANCE(3);
+ if (lookahead != 0) ADVANCE(3);
+ END_STATE();
+ case 2:
+ ADVANCE_MAP(
+ '"', 3,
+ '\'', 4,
+ '(', 37,
+ ')', 38,
+ '/', 39,
+ '[', 35,
+ ']', 36,
+ '{', 26,
+ '}', 28,
+ );
+ if (('\t' <= lookahead && lookahead <= '\r') ||
+ lookahead == ' ') SKIP(2);
+ if (lookahead != 0) ADVANCE(40);
+ END_STATE();
+ case 3:
+ if (lookahead == '"') ADVANCE(43);
+ if (lookahead == '\\') ADVANCE(1);
+ if (lookahead != 0) ADVANCE(3);
+ END_STATE();
+ case 4:
+ if (lookahead == '\'') ADVANCE(43);
+ if (lookahead == '\\') ADVANCE(13);
+ if (lookahead != 0 &&
+ lookahead != '\n' &&
+ lookahead != '\r') ADVANCE(4);
+ END_STATE();
+ case 5:
+ if (lookahead == '*') ADVANCE(6);
+ if (lookahead != 0) ADVANCE(10);
+ END_STATE();
+ case 6:
+ if (lookahead == '*') ADVANCE(7);
+ if (lookahead == '/') ADVANCE(46);
+ if (lookahead != 0) ADVANCE(8);
+ END_STATE();
+ case 7:
+ if (lookahead == '*') ADVANCE(7);
+ if (lookahead == '/') ADVANCE(44);
+ if (lookahead != 0) ADVANCE(8);
+ END_STATE();
+ case 8:
+ if (lookahead == '*') ADVANCE(7);
+ if (lookahead != 0) ADVANCE(8);
+ END_STATE();
+ case 9:
+ if (lookahead == '*') ADVANCE(9);
+ if (lookahead == '/') ADVANCE(45);
+ if (lookahead != 0) ADVANCE(10);
+ END_STATE();
+ case 10:
+ if (lookahead == '*') ADVANCE(9);
+ if (lookahead != 0) ADVANCE(10);
+ END_STATE();
+ case 11:
+ if (lookahead == '.') ADVANCE(34);
+ END_STATE();
+ case 12:
+ if (lookahead == '/') ADVANCE(16);
+ if (lookahead == '\n' ||
+ lookahead == '\r') SKIP(12);
+ if (('\t' <= lookahead && lookahead <= '\f') ||
+ lookahead == ' ') ADVANCE(23);
+ if (lookahead != 0 &&
+ lookahead != ';' &&
+ lookahead != '{' &&
+ lookahead != '}') ADVANCE(25);
+ END_STATE();
+ case 13:
+ if (lookahead != 0 &&
+ lookahead != '\n') ADVANCE(4);
+ END_STATE();
+ case 14:
+ ACCEPT_TOKEN(ts_builtin_sym_end);
+ END_STATE();
+ case 15:
+ ACCEPT_TOKEN(anon_sym_SEMI);
+ END_STATE();
+ case 16:
+ ACCEPT_TOKEN(sym_version_constraint);
+ if (lookahead == '*') ADVANCE(17);
+ if (lookahead == '/') ADVANCE(24);
+ if (lookahead != 0 &&
+ lookahead != '\n' &&
+ lookahead != '\r' &&
+ lookahead != ';' &&
+ lookahead != '{' &&
+ lookahead != '}') ADVANCE(25);
+ END_STATE();
+ case 17:
+ ACCEPT_TOKEN(sym_version_constraint);
+ if (lookahead == '*') ADVANCE(19);
+ if (lookahead == '\n' ||
+ lookahead == '\r' ||
+ lookahead == ';' ||
+ lookahead == '{' ||
+ lookahead == '}') ADVANCE(10);
+ if (lookahead != 0) ADVANCE(22);
+ END_STATE();
+ case 18:
+ ACCEPT_TOKEN(sym_version_constraint);
+ if (lookahead == '*') ADVANCE(18);
+ if (lookahead == '/') ADVANCE(44);
+ if (lookahead == '\n' ||
+ lookahead == '\r' ||
+ lookahead == ';' ||
+ lookahead == '{' ||
+ lookahead == '}') ADVANCE(8);
+ if (lookahead != 0) ADVANCE(20);
+ END_STATE();
+ case 19:
+ ACCEPT_TOKEN(sym_version_constraint);
+ if (lookahead == '*') ADVANCE(18);
+ if (lookahead == '/') ADVANCE(47);
+ if (lookahead == '\n' ||
+ lookahead == '\r' ||
+ lookahead == ';' ||
+ lookahead == '{' ||
+ lookahead == '}') ADVANCE(8);
+ if (lookahead != 0) ADVANCE(20);
+ END_STATE();
+ case 20:
+ ACCEPT_TOKEN(sym_version_constraint);
+ if (lookahead == '*') ADVANCE(18);
+ if (lookahead == '\n' ||
+ lookahead == '\r' ||
+ lookahead == ';' ||
+ lookahead == '{' ||
+ lookahead == '}') ADVANCE(8);
+ if (lookahead != 0) ADVANCE(20);
+ END_STATE();
+ case 21:
+ ACCEPT_TOKEN(sym_version_constraint);
+ if (lookahead == '*') ADVANCE(21);
+ if (lookahead == '/') ADVANCE(45);
+ if (lookahead == '\n' ||
+ lookahead == '\r' ||
+ lookahead == ';' ||
+ lookahead == '{' ||
+ lookahead == '}') ADVANCE(10);
+ if (lookahead != 0) ADVANCE(22);
+ END_STATE();
+ case 22:
+ ACCEPT_TOKEN(sym_version_constraint);
+ if (lookahead == '*') ADVANCE(21);
+ if (lookahead == '\n' ||
+ lookahead == '\r' ||
+ lookahead == ';' ||
+ lookahead == '{' ||
+ lookahead == '}') ADVANCE(10);
+ if (lookahead != 0) ADVANCE(22);
+ END_STATE();
+ case 23:
+ ACCEPT_TOKEN(sym_version_constraint);
+ if (lookahead == '/') ADVANCE(16);
+ if (lookahead == '\t' ||
+ lookahead == 0x0b ||
+ lookahead == '\f' ||
+ lookahead == ' ') ADVANCE(23);
+ if (lookahead != 0 &&
+ (lookahead < '\t' || '\r' < lookahead) &&
+ lookahead != ';' &&
+ lookahead != '{' &&
+ lookahead != '}') ADVANCE(25);
+ END_STATE();
+ case 24:
+ ACCEPT_TOKEN(sym_version_constraint);
+ if (lookahead == ';' ||
+ lookahead == '{' ||
+ lookahead == '}') ADVANCE(48);
+ if (lookahead != 0 &&
+ lookahead != '\n' &&
+ lookahead != '\r') ADVANCE(24);
+ END_STATE();
+ case 25:
+ ACCEPT_TOKEN(sym_version_constraint);
+ if (lookahead != 0 &&
+ lookahead != '\n' &&
+ lookahead != '\r' &&
+ lookahead != ';' &&
+ lookahead != '{' &&
+ lookahead != '}') ADVANCE(25);
+ END_STATE();
+ case 26:
+ ACCEPT_TOKEN(anon_sym_LBRACE);
+ END_STATE();
+ case 27:
+ ACCEPT_TOKEN(anon_sym_COMMA);
+ END_STATE();
+ case 28:
+ ACCEPT_TOKEN(anon_sym_RBRACE);
+ END_STATE();
+ case 29:
+ ACCEPT_TOKEN(anon_sym_EQ);
+ END_STATE();
+ case 30:
+ ACCEPT_TOKEN(anon_sym_COLON);
+ END_STATE();
+ case 31:
+ ACCEPT_TOKEN(anon_sym_LT);
+ END_STATE();
+ case 32:
+ ACCEPT_TOKEN(anon_sym_GT);
+ END_STATE();
+ case 33:
+ ACCEPT_TOKEN(anon_sym_POUND);
+ END_STATE();
+ case 34:
+ ACCEPT_TOKEN(anon_sym_DOT_DOT);
+ END_STATE();
+ case 35:
+ ACCEPT_TOKEN(anon_sym_LBRACK);
+ END_STATE();
+ case 36:
+ ACCEPT_TOKEN(anon_sym_RBRACK);
+ END_STATE();
+ case 37:
+ ACCEPT_TOKEN(anon_sym_LPAREN);
+ END_STATE();
+ case 38:
+ ACCEPT_TOKEN(anon_sym_RPAREN);
+ END_STATE();
+ case 39:
+ ACCEPT_TOKEN(anon_sym_SLASH);
+ if (lookahead == '*') ADVANCE(5);
+ if (lookahead == '/') ADVANCE(48);
+ END_STATE();
+ case 40:
+ ACCEPT_TOKEN(sym__opaque_token);
+ if ((!eof && set_contains(sym__opaque_token_character_set_1, 10, lookahead))) ADVANCE(40);
+ END_STATE();
+ case 41:
+ ACCEPT_TOKEN(sym_identifier);
+ if (lookahead == '$' ||
+ ('0' <= lookahead && lookahead <= '9') ||
+ ('A' <= lookahead && lookahead <= 'Z') ||
+ lookahead == '_' ||
+ ('a' <= lookahead && lookahead <= 'z')) ADVANCE(41);
+ END_STATE();
+ case 42:
+ ACCEPT_TOKEN(sym_number);
+ if (('0' <= lookahead && lookahead <= '9')) ADVANCE(42);
+ END_STATE();
+ case 43:
+ ACCEPT_TOKEN(sym_string);
+ END_STATE();
+ case 44:
+ ACCEPT_TOKEN(sym_doc_comment);
+ END_STATE();
+ case 45:
+ ACCEPT_TOKEN(sym_block_comment);
+ END_STATE();
+ case 46:
+ ACCEPT_TOKEN(sym_block_comment);
+ if (lookahead == '*') ADVANCE(7);
+ if (lookahead != 0) ADVANCE(8);
+ END_STATE();
+ case 47:
+ ACCEPT_TOKEN(sym_block_comment);
+ if (lookahead == '*') ADVANCE(18);
+ if (lookahead == '\n' ||
+ lookahead == '\r' ||
+ lookahead == ';' ||
+ lookahead == '{' ||
+ lookahead == '}') ADVANCE(8);
+ if (lookahead != 0) ADVANCE(20);
+ END_STATE();
+ case 48:
+ ACCEPT_TOKEN(sym_line_comment);
+ if (lookahead != 0 &&
+ lookahead != '\n' &&
+ lookahead != '\r') ADVANCE(48);
+ END_STATE();
+ default:
+ return false;
+ }
+}
+
+static bool ts_lex_keywords(TSLexer *lexer, TSStateId state) {
+ START_LEXER();
+ eof = lexer->eof(lexer);
+ switch (state) {
+ case 0:
+ ADVANCE_MAP(
+ 'a', 1,
+ 'c', 2,
+ 'e', 3,
+ 'f', 4,
+ 'i', 5,
+ 'l', 6,
+ 'm', 7,
+ 'n', 8,
+ 'p', 9,
+ 's', 10,
+ 't', 11,
+ 'w', 12,
+ );
+ if (('\t' <= lookahead && lookahead <= '\r') ||
+ lookahead == ' ') SKIP(0);
+ END_STATE();
+ case 1:
+ if (lookahead == 's') ADVANCE(13);
+ END_STATE();
+ case 2:
+ if (lookahead == 'i') ADVANCE(14);
+ if (lookahead == 'o') ADVANCE(15);
+ END_STATE();
+ case 3:
+ if (lookahead == 'n') ADVANCE(16);
+ if (lookahead == 'x') ADVANCE(17);
+ END_STATE();
+ case 4:
+ if (lookahead == 'r') ADVANCE(18);
+ END_STATE();
+ case 5:
+ if (lookahead == 'm') ADVANCE(19);
+ if (lookahead == 'n') ADVANCE(20);
+ END_STATE();
+ case 6:
+ if (lookahead == 'e') ADVANCE(21);
+ END_STATE();
+ case 7:
+ if (lookahead == 'o') ADVANCE(22);
+ END_STATE();
+ case 8:
+ if (lookahead == 'e') ADVANCE(23);
+ END_STATE();
+ case 9:
+ if (lookahead == 'r') ADVANCE(24);
+ if (lookahead == 'u') ADVANCE(25);
+ END_STATE();
+ case 10:
+ if (lookahead == 'e') ADVANCE(26);
+ if (lookahead == 't') ADVANCE(27);
+ END_STATE();
+ case 11:
+ if (lookahead == 'y') ADVANCE(28);
+ END_STATE();
+ case 12:
+ if (lookahead == 'i') ADVANCE(29);
+ END_STATE();
+ case 13:
+ ACCEPT_TOKEN(anon_sym_as);
+ END_STATE();
+ case 14:
+ if (lookahead == 'r') ADVANCE(30);
+ END_STATE();
+ case 15:
+ if (lookahead == 'n') ADVANCE(31);
+ END_STATE();
+ case 16:
+ if (lookahead == 'u') ADVANCE(32);
+ END_STATE();
+ case 17:
+ if (lookahead == 'p') ADVANCE(33);
+ END_STATE();
+ case 18:
+ if (lookahead == 'o') ADVANCE(34);
+ END_STATE();
+ case 19:
+ if (lookahead == 'p') ADVANCE(35);
+ END_STATE();
+ case 20:
+ if (lookahead == 'c') ADVANCE(36);
+ END_STATE();
+ case 21:
+ if (lookahead == 'd') ADVANCE(37);
+ END_STATE();
+ case 22:
+ if (lookahead == 'd') ADVANCE(38);
+ END_STATE();
+ case 23:
+ if (lookahead == 'w') ADVANCE(39);
+ END_STATE();
+ case 24:
+ if (lookahead == 'a') ADVANCE(40);
+ if (lookahead == 'e') ADVANCE(41);
+ END_STATE();
+ case 25:
+ if (lookahead == 'r') ADVANCE(42);
+ END_STATE();
+ case 26:
+ if (lookahead == 'a') ADVANCE(43);
+ END_STATE();
+ case 27:
+ if (lookahead == 'r') ADVANCE(44);
+ END_STATE();
+ case 28:
+ if (lookahead == 'p') ADVANCE(45);
+ END_STATE();
+ case 29:
+ if (lookahead == 't') ADVANCE(46);
+ END_STATE();
+ case 30:
+ if (lookahead == 'c') ADVANCE(47);
+ END_STATE();
+ case 31:
+ if (lookahead == 's') ADVANCE(48);
+ if (lookahead == 't') ADVANCE(49);
+ END_STATE();
+ case 32:
+ if (lookahead == 'm') ADVANCE(50);
+ END_STATE();
+ case 33:
+ if (lookahead == 'o') ADVANCE(51);
+ END_STATE();
+ case 34:
+ if (lookahead == 'm') ADVANCE(52);
+ END_STATE();
+ case 35:
+ if (lookahead == 'l') ADVANCE(53);
+ if (lookahead == 'o') ADVANCE(54);
+ END_STATE();
+ case 36:
+ if (lookahead == 'l') ADVANCE(55);
+ END_STATE();
+ case 37:
+ if (lookahead == 'g') ADVANCE(56);
+ END_STATE();
+ case 38:
+ if (lookahead == 'u') ADVANCE(57);
+ END_STATE();
+ case 39:
+ ACCEPT_TOKEN(anon_sym_new);
+ END_STATE();
+ case 40:
+ if (lookahead == 'g') ADVANCE(58);
+ END_STATE();
+ case 41:
+ if (lookahead == 'f') ADVANCE(59);
+ END_STATE();
+ case 42:
+ if (lookahead == 'e') ADVANCE(60);
+ END_STATE();
+ case 43:
+ if (lookahead == 'l') ADVANCE(61);
+ END_STATE();
+ case 44:
+ if (lookahead == 'u') ADVANCE(62);
+ END_STATE();
+ case 45:
+ if (lookahead == 'e') ADVANCE(63);
+ END_STATE();
+ case 46:
+ if (lookahead == 'n') ADVANCE(64);
+ END_STATE();
+ case 47:
+ if (lookahead == 'u') ADVANCE(65);
+ END_STATE();
+ case 48:
+ if (lookahead == 't') ADVANCE(66);
+ END_STATE();
+ case 49:
+ if (lookahead == 'r') ADVANCE(67);
+ END_STATE();
+ case 50:
+ ACCEPT_TOKEN(anon_sym_enum);
+ END_STATE();
+ case 51:
+ if (lookahead == 'r') ADVANCE(68);
+ END_STATE();
+ case 52:
+ ACCEPT_TOKEN(anon_sym_from);
+ END_STATE();
+ case 53:
+ if (lookahead == 'e') ADVANCE(69);
+ END_STATE();
+ case 54:
+ if (lookahead == 'r') ADVANCE(70);
+ END_STATE();
+ case 55:
+ if (lookahead == 'u') ADVANCE(71);
+ END_STATE();
+ case 56:
+ if (lookahead == 'e') ADVANCE(72);
+ END_STATE();
+ case 57:
+ if (lookahead == 'l') ADVANCE(73);
+ END_STATE();
+ case 58:
+ if (lookahead == 'm') ADVANCE(74);
+ END_STATE();
+ case 59:
+ if (lookahead == 'i') ADVANCE(75);
+ END_STATE();
+ case 60:
+ ACCEPT_TOKEN(anon_sym_pure);
+ END_STATE();
+ case 61:
+ if (lookahead == 'e') ADVANCE(76);
+ END_STATE();
+ case 62:
+ if (lookahead == 'c') ADVANCE(77);
+ END_STATE();
+ case 63:
+ ACCEPT_TOKEN(anon_sym_type);
+ END_STATE();
+ case 64:
+ if (lookahead == 'e') ADVANCE(78);
+ END_STATE();
+ case 65:
+ if (lookahead == 'i') ADVANCE(79);
+ END_STATE();
+ case 66:
+ if (lookahead == 'r') ADVANCE(80);
+ END_STATE();
+ case 67:
+ if (lookahead == 'a') ADVANCE(81);
+ END_STATE();
+ case 68:
+ if (lookahead == 't') ADVANCE(82);
+ END_STATE();
+ case 69:
+ if (lookahead == 'm') ADVANCE(83);
+ END_STATE();
+ case 70:
+ if (lookahead == 't') ADVANCE(84);
+ END_STATE();
+ case 71:
+ if (lookahead == 'd') ADVANCE(85);
+ END_STATE();
+ case 72:
+ if (lookahead == 'r') ADVANCE(86);
+ END_STATE();
+ case 73:
+ if (lookahead == 'e') ADVANCE(87);
+ END_STATE();
+ case 74:
+ if (lookahead == 'a') ADVANCE(88);
+ END_STATE();
+ case 75:
+ if (lookahead == 'x') ADVANCE(89);
+ END_STATE();
+ case 76:
+ if (lookahead == 'd') ADVANCE(90);
+ END_STATE();
+ case 77:
+ if (lookahead == 't') ADVANCE(91);
+ END_STATE();
+ case 78:
+ if (lookahead == 's') ADVANCE(92);
+ END_STATE();
+ case 79:
+ if (lookahead == 't') ADVANCE(93);
+ END_STATE();
+ case 80:
+ if (lookahead == 'u') ADVANCE(94);
+ END_STATE();
+ case 81:
+ if (lookahead == 'c') ADVANCE(95);
+ END_STATE();
+ case 82:
+ ACCEPT_TOKEN(anon_sym_export);
+ END_STATE();
+ case 83:
+ if (lookahead == 'e') ADVANCE(96);
+ END_STATE();
+ case 84:
+ ACCEPT_TOKEN(anon_sym_import);
+ END_STATE();
+ case 85:
+ if (lookahead == 'e') ADVANCE(97);
+ END_STATE();
+ case 86:
+ ACCEPT_TOKEN(anon_sym_ledger);
+ END_STATE();
+ case 87:
+ ACCEPT_TOKEN(anon_sym_module);
+ END_STATE();
+ case 88:
+ ACCEPT_TOKEN(anon_sym_pragma);
+ END_STATE();
+ case 89:
+ ACCEPT_TOKEN(anon_sym_prefix);
+ END_STATE();
+ case 90:
+ ACCEPT_TOKEN(anon_sym_sealed);
+ END_STATE();
+ case 91:
+ ACCEPT_TOKEN(anon_sym_struct);
+ END_STATE();
+ case 92:
+ if (lookahead == 's') ADVANCE(98);
+ END_STATE();
+ case 93:
+ ACCEPT_TOKEN(anon_sym_circuit);
+ END_STATE();
+ case 94:
+ if (lookahead == 'c') ADVANCE(99);
+ END_STATE();
+ case 95:
+ if (lookahead == 't') ADVANCE(100);
+ END_STATE();
+ case 96:
+ if (lookahead == 'n') ADVANCE(101);
+ END_STATE();
+ case 97:
+ ACCEPT_TOKEN(anon_sym_include);
+ END_STATE();
+ case 98:
+ ACCEPT_TOKEN(anon_sym_witness);
+ END_STATE();
+ case 99:
+ if (lookahead == 't') ADVANCE(102);
+ END_STATE();
+ case 100:
+ ACCEPT_TOKEN(anon_sym_contract);
+ END_STATE();
+ case 101:
+ if (lookahead == 't') ADVANCE(103);
+ END_STATE();
+ case 102:
+ if (lookahead == 'o') ADVANCE(104);
+ END_STATE();
+ case 103:
+ if (lookahead == 's') ADVANCE(105);
+ END_STATE();
+ case 104:
+ if (lookahead == 'r') ADVANCE(106);
+ END_STATE();
+ case 105:
+ ACCEPT_TOKEN(anon_sym_implements);
+ END_STATE();
+ case 106:
+ ACCEPT_TOKEN(anon_sym_constructor);
+ END_STATE();
+ default:
+ return false;
+ }
+}
+
+static const TSLexerMode ts_lex_modes[STATE_COUNT] = {
+ [0] = {.lex_state = 0},
+ [1] = {.lex_state = 0},
+ [2] = {.lex_state = 0},
+ [3] = {.lex_state = 0},
+ [4] = {.lex_state = 0},
+ [5] = {.lex_state = 0},
+ [6] = {.lex_state = 0},
+ [7] = {.lex_state = 0},
+ [8] = {.lex_state = 0},
+ [9] = {.lex_state = 0},
+ [10] = {.lex_state = 0},
+ [11] = {.lex_state = 0},
+ [12] = {.lex_state = 0},
+ [13] = {.lex_state = 0},
+ [14] = {.lex_state = 0},
+ [15] = {.lex_state = 0},
+ [16] = {.lex_state = 0},
+ [17] = {.lex_state = 0},
+ [18] = {.lex_state = 0},
+ [19] = {.lex_state = 0},
+ [20] = {.lex_state = 0},
+ [21] = {.lex_state = 0},
+ [22] = {.lex_state = 0},
+ [23] = {.lex_state = 0},
+ [24] = {.lex_state = 0},
+ [25] = {.lex_state = 0},
+ [26] = {.lex_state = 0},
+ [27] = {.lex_state = 0},
+ [28] = {.lex_state = 0},
+ [29] = {.lex_state = 0},
+ [30] = {.lex_state = 0},
+ [31] = {.lex_state = 0},
+ [32] = {.lex_state = 0},
+ [33] = {.lex_state = 0},
+ [34] = {.lex_state = 0},
+ [35] = {.lex_state = 0},
+ [36] = {.lex_state = 0},
+ [37] = {.lex_state = 0},
+ [38] = {.lex_state = 0},
+ [39] = {.lex_state = 0},
+ [40] = {.lex_state = 0},
+ [41] = {.lex_state = 0},
+ [42] = {.lex_state = 0},
+ [43] = {.lex_state = 0},
+ [44] = {.lex_state = 0},
+ [45] = {.lex_state = 0},
+ [46] = {.lex_state = 0},
+ [47] = {.lex_state = 0},
+ [48] = {.lex_state = 0},
+ [49] = {.lex_state = 0},
+ [50] = {.lex_state = 0},
+ [51] = {.lex_state = 0},
+ [52] = {.lex_state = 0},
+ [53] = {.lex_state = 0},
+ [54] = {.lex_state = 0},
+ [55] = {.lex_state = 0},
+ [56] = {.lex_state = 0},
+ [57] = {.lex_state = 0},
+ [58] = {.lex_state = 0},
+ [59] = {.lex_state = 0},
+ [60] = {.lex_state = 0},
+ [61] = {.lex_state = 0},
+ [62] = {.lex_state = 0},
+ [63] = {.lex_state = 0},
+ [64] = {.lex_state = 0},
+ [65] = {.lex_state = 0},
+ [66] = {.lex_state = 0},
+ [67] = {.lex_state = 0},
+ [68] = {.lex_state = 2},
+ [69] = {.lex_state = 0},
+ [70] = {.lex_state = 0},
+ [71] = {.lex_state = 0},
+ [72] = {.lex_state = 0},
+ [73] = {.lex_state = 0},
+ [74] = {.lex_state = 0},
+ [75] = {.lex_state = 0},
+ [76] = {.lex_state = 0},
+ [77] = {.lex_state = 0},
+ [78] = {.lex_state = 0},
+ [79] = {.lex_state = 0},
+ [80] = {.lex_state = 0},
+ [81] = {.lex_state = 0},
+ [82] = {.lex_state = 0},
+ [83] = {.lex_state = 0},
+ [84] = {.lex_state = 0},
+ [85] = {.lex_state = 0},
+ [86] = {.lex_state = 0},
+ [87] = {.lex_state = 0},
+ [88] = {.lex_state = 0},
+ [89] = {.lex_state = 0},
+ [90] = {.lex_state = 0},
+ [91] = {.lex_state = 0},
+ [92] = {.lex_state = 0},
+ [93] = {.lex_state = 0},
+ [94] = {.lex_state = 0},
+ [95] = {.lex_state = 0},
+ [96] = {.lex_state = 2},
+ [97] = {.lex_state = 2},
+ [98] = {.lex_state = 2},
+ [99] = {.lex_state = 0},
+ [100] = {.lex_state = 2},
+ [101] = {.lex_state = 2},
+ [102] = {.lex_state = 0},
+ [103] = {.lex_state = 2},
+ [104] = {.lex_state = 2},
+ [105] = {.lex_state = 0},
+ [106] = {.lex_state = 2},
+ [107] = {.lex_state = 2},
+ [108] = {.lex_state = 2},
+ [109] = {.lex_state = 2},
+ [110] = {.lex_state = 2},
+ [111] = {.lex_state = 2},
+ [112] = {.lex_state = 2},
+ [113] = {.lex_state = 2},
+ [114] = {.lex_state = 2},
+ [115] = {.lex_state = 0},
+ [116] = {.lex_state = 0},
+ [117] = {.lex_state = 0},
+ [118] = {.lex_state = 0},
+ [119] = {.lex_state = 0},
+ [120] = {.lex_state = 0},
+ [121] = {.lex_state = 0},
+ [122] = {.lex_state = 0},
+ [123] = {.lex_state = 0},
+ [124] = {.lex_state = 0},
+ [125] = {.lex_state = 0},
+ [126] = {.lex_state = 0},
+ [127] = {.lex_state = 0},
+ [128] = {.lex_state = 0},
+ [129] = {.lex_state = 0},
+ [130] = {.lex_state = 0},
+ [131] = {.lex_state = 0},
+ [132] = {.lex_state = 0},
+ [133] = {.lex_state = 0},
+ [134] = {.lex_state = 0},
+ [135] = {.lex_state = 0},
+ [136] = {.lex_state = 0},
+ [137] = {.lex_state = 0},
+ [138] = {.lex_state = 0},
+ [139] = {.lex_state = 0},
+ [140] = {.lex_state = 0},
+ [141] = {.lex_state = 0},
+ [142] = {.lex_state = 0},
+ [143] = {.lex_state = 0},
+ [144] = {.lex_state = 0},
+ [145] = {.lex_state = 0},
+ [146] = {.lex_state = 0},
+ [147] = {.lex_state = 0},
+ [148] = {.lex_state = 0},
+ [149] = {.lex_state = 0},
+ [150] = {.lex_state = 0},
+ [151] = {.lex_state = 0},
+ [152] = {.lex_state = 0},
+ [153] = {.lex_state = 0},
+ [154] = {.lex_state = 0},
+ [155] = {.lex_state = 0},
+ [156] = {.lex_state = 0},
+ [157] = {.lex_state = 0},
+ [158] = {.lex_state = 0},
+ [159] = {.lex_state = 0},
+ [160] = {.lex_state = 0},
+ [161] = {.lex_state = 0},
+ [162] = {.lex_state = 0},
+ [163] = {.lex_state = 0},
+ [164] = {.lex_state = 0},
+ [165] = {.lex_state = 0},
+ [166] = {.lex_state = 0},
+ [167] = {.lex_state = 0},
+ [168] = {.lex_state = 0},
+ [169] = {.lex_state = 0},
+ [170] = {.lex_state = 0},
+ [171] = {.lex_state = 0},
+ [172] = {.lex_state = 0},
+ [173] = {.lex_state = 0},
+ [174] = {.lex_state = 0},
+ [175] = {.lex_state = 0},
+ [176] = {.lex_state = 0},
+ [177] = {.lex_state = 0},
+ [178] = {.lex_state = 0},
+ [179] = {.lex_state = 0},
+ [180] = {.lex_state = 0},
+ [181] = {.lex_state = 0},
+ [182] = {.lex_state = 0},
+ [183] = {.lex_state = 0},
+ [184] = {.lex_state = 0},
+ [185] = {.lex_state = 0},
+ [186] = {.lex_state = 0},
+ [187] = {.lex_state = 0},
+ [188] = {.lex_state = 0},
+ [189] = {.lex_state = 0},
+ [190] = {.lex_state = 0},
+ [191] = {.lex_state = 0},
+ [192] = {.lex_state = 0},
+ [193] = {.lex_state = 0},
+ [194] = {.lex_state = 0},
+ [195] = {.lex_state = 0},
+ [196] = {.lex_state = 0},
+ [197] = {.lex_state = 0},
+ [198] = {.lex_state = 0},
+ [199] = {.lex_state = 0},
+ [200] = {.lex_state = 0},
+ [201] = {.lex_state = 0},
+ [202] = {.lex_state = 0},
+ [203] = {.lex_state = 0},
+ [204] = {.lex_state = 0},
+ [205] = {.lex_state = 0},
+ [206] = {.lex_state = 0},
+ [207] = {.lex_state = 0},
+ [208] = {.lex_state = 0},
+ [209] = {.lex_state = 0},
+ [210] = {.lex_state = 0},
+ [211] = {.lex_state = 0},
+ [212] = {.lex_state = 0},
+ [213] = {.lex_state = 0},
+ [214] = {.lex_state = 0},
+ [215] = {.lex_state = 0},
+ [216] = {.lex_state = 0},
+ [217] = {.lex_state = 0},
+ [218] = {.lex_state = 0},
+ [219] = {.lex_state = 0},
+ [220] = {.lex_state = 0},
+ [221] = {.lex_state = 0},
+ [222] = {.lex_state = 0},
+ [223] = {.lex_state = 0},
+ [224] = {.lex_state = 0},
+ [225] = {.lex_state = 0},
+ [226] = {.lex_state = 12},
+ [227] = {.lex_state = 0},
+ [228] = {.lex_state = 0},
+ [229] = {.lex_state = 0},
+ [230] = {.lex_state = 0},
+ [231] = {.lex_state = 0},
+ [232] = {.lex_state = 0},
+ [233] = {.lex_state = 0},
+ [234] = {.lex_state = 0},
+ [235] = {.lex_state = 0},
+ [236] = {.lex_state = 0},
+ [237] = {.lex_state = 0},
+ [238] = {.lex_state = 0},
+ [239] = {.lex_state = 0},
+ [240] = {.lex_state = 0},
+ [241] = {.lex_state = 0},
+ [242] = {.lex_state = 0},
+ [243] = {.lex_state = 0},
+ [244] = {.lex_state = 0},
+ [245] = {.lex_state = 0},
+ [246] = {.lex_state = 0},
+ [247] = {.lex_state = 0},
+ [248] = {.lex_state = 0},
+ [249] = {.lex_state = 0},
+ [250] = {.lex_state = 0},
+ [251] = {.lex_state = 0},
+ [252] = {.lex_state = 0},
+ [253] = {.lex_state = 0},
+ [254] = {.lex_state = 0},
+ [255] = {.lex_state = 0},
+ [256] = {.lex_state = 0},
+ [257] = {.lex_state = 0},
+ [258] = {.lex_state = 0},
+ [259] = {.lex_state = 0},
+ [260] = {.lex_state = 0},
+ [261] = {.lex_state = 0},
+ [262] = {.lex_state = 0},
+ [263] = {.lex_state = 0},
+ [264] = {.lex_state = 0},
+};
+
+static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = {
+ [STATE(0)] = {
+ [ts_builtin_sym_end] = ACTIONS(1),
+ [sym_identifier] = ACTIONS(1),
+ [anon_sym_pragma] = ACTIONS(1),
+ [anon_sym_SEMI] = ACTIONS(1),
+ [anon_sym_include] = ACTIONS(1),
+ [anon_sym_import] = ACTIONS(1),
+ [anon_sym_LBRACE] = ACTIONS(1),
+ [anon_sym_COMMA] = ACTIONS(1),
+ [anon_sym_RBRACE] = ACTIONS(1),
+ [anon_sym_from] = ACTIONS(1),
+ [anon_sym_as] = ACTIONS(1),
+ [anon_sym_prefix] = ACTIONS(1),
+ [anon_sym_export] = ACTIONS(1),
+ [anon_sym_module] = ACTIONS(1),
+ [anon_sym_struct] = ACTIONS(1),
+ [anon_sym_enum] = ACTIONS(1),
+ [anon_sym_contract] = ACTIONS(1),
+ [anon_sym_implements] = ACTIONS(1),
+ [anon_sym_new] = ACTIONS(1),
+ [anon_sym_type] = ACTIONS(1),
+ [anon_sym_EQ] = ACTIONS(1),
+ [anon_sym_sealed] = ACTIONS(1),
+ [anon_sym_ledger] = ACTIONS(1),
+ [anon_sym_COLON] = ACTIONS(1),
+ [anon_sym_witness] = ACTIONS(1),
+ [anon_sym_constructor] = ACTIONS(1),
+ [anon_sym_pure] = ACTIONS(1),
+ [anon_sym_circuit] = ACTIONS(1),
+ [anon_sym_LT] = ACTIONS(1),
+ [anon_sym_GT] = ACTIONS(1),
+ [anon_sym_POUND] = ACTIONS(1),
+ [anon_sym_DOT_DOT] = ACTIONS(1),
+ [anon_sym_LBRACK] = ACTIONS(1),
+ [anon_sym_RBRACK] = ACTIONS(1),
+ [anon_sym_LPAREN] = ACTIONS(1),
+ [anon_sym_RPAREN] = ACTIONS(1),
+ [anon_sym_SLASH] = ACTIONS(1),
+ [sym_number] = ACTIONS(1),
+ [sym_string] = ACTIONS(1),
+ [sym_doc_comment] = ACTIONS(3),
+ [sym_block_comment] = ACTIONS(5),
+ [sym_line_comment] = ACTIONS(3),
+ },
+ [STATE(1)] = {
+ [sym_source_file] = STATE(207),
+ [sym__declaration] = STATE(3),
+ [sym_pragma_declaration] = STATE(3),
+ [sym_include_declaration] = STATE(3),
+ [sym_import_declaration] = STATE(3),
+ [sym_export_declaration] = STATE(3),
+ [sym_module_declaration] = STATE(3),
+ [sym_struct_declaration] = STATE(3),
+ [sym_enum_declaration] = STATE(3),
+ [sym_implements_declaration] = STATE(3),
+ [sym_contract_declaration] = STATE(3),
+ [sym_type_declaration] = STATE(3),
+ [sym_ledger_declaration] = STATE(3),
+ [sym_witness_declaration] = STATE(3),
+ [sym_constructor_declaration] = STATE(3),
+ [sym_circuit_declaration] = STATE(3),
+ [aux_sym_source_file_repeat1] = STATE(3),
+ [ts_builtin_sym_end] = ACTIONS(7),
+ [anon_sym_pragma] = ACTIONS(9),
+ [anon_sym_include] = ACTIONS(11),
+ [anon_sym_import] = ACTIONS(13),
+ [anon_sym_export] = ACTIONS(15),
+ [anon_sym_module] = ACTIONS(17),
+ [anon_sym_struct] = ACTIONS(19),
+ [anon_sym_enum] = ACTIONS(21),
+ [anon_sym_contract] = ACTIONS(23),
+ [anon_sym_new] = ACTIONS(25),
+ [anon_sym_type] = ACTIONS(27),
+ [anon_sym_sealed] = ACTIONS(29),
+ [anon_sym_ledger] = ACTIONS(31),
+ [anon_sym_witness] = ACTIONS(33),
+ [anon_sym_constructor] = ACTIONS(35),
+ [anon_sym_pure] = ACTIONS(37),
+ [anon_sym_circuit] = ACTIONS(39),
+ [sym_doc_comment] = ACTIONS(3),
+ [sym_block_comment] = ACTIONS(5),
+ [sym_line_comment] = ACTIONS(3),
+ },
+};
+
+static const uint16_t ts_small_parse_table[] = {
+ [0] = 20,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(43), 1,
+ anon_sym_pragma,
+ ACTIONS(46), 1,
+ anon_sym_include,
+ ACTIONS(49), 1,
+ anon_sym_import,
+ ACTIONS(52), 1,
+ anon_sym_export,
+ ACTIONS(55), 1,
+ anon_sym_module,
+ ACTIONS(58), 1,
+ anon_sym_struct,
+ ACTIONS(61), 1,
+ anon_sym_enum,
+ ACTIONS(64), 1,
+ anon_sym_contract,
+ ACTIONS(67), 1,
+ anon_sym_new,
+ ACTIONS(70), 1,
+ anon_sym_type,
+ ACTIONS(73), 1,
+ anon_sym_sealed,
+ ACTIONS(76), 1,
+ anon_sym_ledger,
+ ACTIONS(79), 1,
+ anon_sym_witness,
+ ACTIONS(82), 1,
+ anon_sym_constructor,
+ ACTIONS(85), 1,
+ anon_sym_pure,
+ ACTIONS(88), 1,
+ anon_sym_circuit,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(41), 2,
+ ts_builtin_sym_end,
+ anon_sym_RBRACE,
+ STATE(2), 16,
+ sym__declaration,
+ sym_pragma_declaration,
+ sym_include_declaration,
+ sym_import_declaration,
+ sym_export_declaration,
+ sym_module_declaration,
+ sym_struct_declaration,
+ sym_enum_declaration,
+ sym_implements_declaration,
+ sym_contract_declaration,
+ sym_type_declaration,
+ sym_ledger_declaration,
+ sym_witness_declaration,
+ sym_constructor_declaration,
+ sym_circuit_declaration,
+ aux_sym_source_file_repeat1,
+ [78] = 20,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(9), 1,
+ anon_sym_pragma,
+ ACTIONS(11), 1,
+ anon_sym_include,
+ ACTIONS(13), 1,
+ anon_sym_import,
+ ACTIONS(15), 1,
+ anon_sym_export,
+ ACTIONS(17), 1,
+ anon_sym_module,
+ ACTIONS(19), 1,
+ anon_sym_struct,
+ ACTIONS(21), 1,
+ anon_sym_enum,
+ ACTIONS(23), 1,
+ anon_sym_contract,
+ ACTIONS(25), 1,
+ anon_sym_new,
+ ACTIONS(27), 1,
+ anon_sym_type,
+ ACTIONS(29), 1,
+ anon_sym_sealed,
+ ACTIONS(31), 1,
+ anon_sym_ledger,
+ ACTIONS(33), 1,
+ anon_sym_witness,
+ ACTIONS(35), 1,
+ anon_sym_constructor,
+ ACTIONS(37), 1,
+ anon_sym_pure,
+ ACTIONS(39), 1,
+ anon_sym_circuit,
+ ACTIONS(91), 1,
+ ts_builtin_sym_end,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(2), 16,
+ sym__declaration,
+ sym_pragma_declaration,
+ sym_include_declaration,
+ sym_import_declaration,
+ sym_export_declaration,
+ sym_module_declaration,
+ sym_struct_declaration,
+ sym_enum_declaration,
+ sym_implements_declaration,
+ sym_contract_declaration,
+ sym_type_declaration,
+ sym_ledger_declaration,
+ sym_witness_declaration,
+ sym_constructor_declaration,
+ sym_circuit_declaration,
+ aux_sym_source_file_repeat1,
+ [155] = 20,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(9), 1,
+ anon_sym_pragma,
+ ACTIONS(11), 1,
+ anon_sym_include,
+ ACTIONS(13), 1,
+ anon_sym_import,
+ ACTIONS(15), 1,
+ anon_sym_export,
+ ACTIONS(17), 1,
+ anon_sym_module,
+ ACTIONS(19), 1,
+ anon_sym_struct,
+ ACTIONS(21), 1,
+ anon_sym_enum,
+ ACTIONS(23), 1,
+ anon_sym_contract,
+ ACTIONS(25), 1,
+ anon_sym_new,
+ ACTIONS(27), 1,
+ anon_sym_type,
+ ACTIONS(29), 1,
+ anon_sym_sealed,
+ ACTIONS(31), 1,
+ anon_sym_ledger,
+ ACTIONS(33), 1,
+ anon_sym_witness,
+ ACTIONS(35), 1,
+ anon_sym_constructor,
+ ACTIONS(37), 1,
+ anon_sym_pure,
+ ACTIONS(39), 1,
+ anon_sym_circuit,
+ ACTIONS(93), 1,
+ anon_sym_RBRACE,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(5), 16,
+ sym__declaration,
+ sym_pragma_declaration,
+ sym_include_declaration,
+ sym_import_declaration,
+ sym_export_declaration,
+ sym_module_declaration,
+ sym_struct_declaration,
+ sym_enum_declaration,
+ sym_implements_declaration,
+ sym_contract_declaration,
+ sym_type_declaration,
+ sym_ledger_declaration,
+ sym_witness_declaration,
+ sym_constructor_declaration,
+ sym_circuit_declaration,
+ aux_sym_source_file_repeat1,
+ [232] = 20,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(9), 1,
+ anon_sym_pragma,
+ ACTIONS(11), 1,
+ anon_sym_include,
+ ACTIONS(13), 1,
+ anon_sym_import,
+ ACTIONS(15), 1,
+ anon_sym_export,
+ ACTIONS(17), 1,
+ anon_sym_module,
+ ACTIONS(19), 1,
+ anon_sym_struct,
+ ACTIONS(21), 1,
+ anon_sym_enum,
+ ACTIONS(23), 1,
+ anon_sym_contract,
+ ACTIONS(25), 1,
+ anon_sym_new,
+ ACTIONS(27), 1,
+ anon_sym_type,
+ ACTIONS(29), 1,
+ anon_sym_sealed,
+ ACTIONS(31), 1,
+ anon_sym_ledger,
+ ACTIONS(33), 1,
+ anon_sym_witness,
+ ACTIONS(35), 1,
+ anon_sym_constructor,
+ ACTIONS(37), 1,
+ anon_sym_pure,
+ ACTIONS(39), 1,
+ anon_sym_circuit,
+ ACTIONS(95), 1,
+ anon_sym_RBRACE,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(2), 16,
+ sym__declaration,
+ sym_pragma_declaration,
+ sym_include_declaration,
+ sym_import_declaration,
+ sym_export_declaration,
+ sym_module_declaration,
+ sym_struct_declaration,
+ sym_enum_declaration,
+ sym_implements_declaration,
+ sym_contract_declaration,
+ sym_type_declaration,
+ sym_ledger_declaration,
+ sym_witness_declaration,
+ sym_constructor_declaration,
+ sym_circuit_declaration,
+ aux_sym_source_file_repeat1,
+ [309] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(99), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(97), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [340] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(103), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(101), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [371] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(107), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(105), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [402] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(111), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(109), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [433] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(115), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(113), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [464] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(119), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(117), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [495] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(123), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(121), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [526] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(127), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(125), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [557] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(131), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(129), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [588] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(133), 19,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_SEMI,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [617] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(137), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(135), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [648] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(141), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(139), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [679] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(143), 19,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_SEMI,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [708] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(147), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(145), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [739] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(149), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [767] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(151), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [795] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(153), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [823] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(155), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [851] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(157), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [879] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(159), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [907] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(161), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [935] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(163), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [963] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(165), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [991] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(167), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1019] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(169), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1047] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(171), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1075] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(173), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1103] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(113), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1131] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(135), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1159] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(175), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1187] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(177), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1215] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(179), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1243] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(181), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1271] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(183), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1299] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(185), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1327] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(187), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1355] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(189), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1383] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(191), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1411] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(193), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1439] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(195), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1467] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(145), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1495] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(197), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1523] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(199), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1551] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(201), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1579] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(203), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1607] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(205), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1635] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(207), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1663] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(209), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1691] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(211), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1719] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(213), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1747] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(215), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1775] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(217), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1803] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(219), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1831] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(221), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1859] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(223), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1887] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(225), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1915] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(227), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1943] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(229), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1971] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(231), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [1999] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(233), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [2027] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(235), 18,
+ ts_builtin_sym_end,
+ anon_sym_pragma,
+ anon_sym_include,
+ anon_sym_import,
+ anon_sym_RBRACE,
+ anon_sym_export,
+ anon_sym_module,
+ anon_sym_struct,
+ anon_sym_enum,
+ anon_sym_contract,
+ anon_sym_new,
+ anon_sym_type,
+ anon_sym_sealed,
+ anon_sym_ledger,
+ anon_sym_witness,
+ anon_sym_constructor,
+ anon_sym_pure,
+ anon_sym_circuit,
+ [2055] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(242), 1,
+ anon_sym_LT,
+ ACTIONS(245), 1,
+ anon_sym_LBRACK,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(240), 4,
+ anon_sym_SEMI,
+ anon_sym_LBRACE,
+ anon_sym_GT,
+ anon_sym_RBRACK,
+ STATE(67), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(237), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2089] = 9,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(248), 1,
+ anon_sym_LBRACE,
+ ACTIONS(253), 1,
+ anon_sym_LBRACK,
+ ACTIONS(256), 1,
+ anon_sym_LPAREN,
+ ACTIONS(259), 1,
+ anon_sym_SLASH,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(262), 2,
+ sym__opaque_token,
+ sym_string,
+ ACTIONS(251), 3,
+ anon_sym_RBRACE,
+ anon_sym_RBRACK,
+ anon_sym_RPAREN,
+ STATE(68), 5,
+ sym_block,
+ sym__paren_group,
+ sym__bracket_group,
+ sym__opaque,
+ aux_sym_block_repeat1,
+ [2125] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(267), 2,
+ anon_sym_SEMI,
+ anon_sym_LBRACE,
+ STATE(67), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(265), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2157] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(163), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2188] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(187), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2219] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ ACTIONS(277), 1,
+ anon_sym_RBRACK,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(79), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(275), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2250] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(223), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2281] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(229), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2312] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(279), 2,
+ anon_sym_prefix,
+ sym_identifier,
+ ACTIONS(281), 11,
+ anon_sym_SEMI,
+ anon_sym_LBRACE,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_LT,
+ anon_sym_GT,
+ anon_sym_DOT_DOT,
+ anon_sym_LBRACK,
+ anon_sym_RBRACK,
+ sym_number,
+ sym_string,
+ [2337] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ ACTIONS(283), 1,
+ anon_sym_GT,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(67), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(265), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2368] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(247), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2399] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(252), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2430] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ ACTIONS(285), 1,
+ anon_sym_RBRACK,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(67), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(265), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2461] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(201), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2492] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(204), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2523] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(287), 2,
+ anon_sym_prefix,
+ sym_identifier,
+ ACTIONS(289), 11,
+ anon_sym_SEMI,
+ anon_sym_LBRACE,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_LT,
+ anon_sym_GT,
+ anon_sym_DOT_DOT,
+ anon_sym_LBRACK,
+ anon_sym_RBRACK,
+ sym_number,
+ sym_string,
+ [2548] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(232), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2579] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(220), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2610] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(221), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2641] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(224), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2672] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(171), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2703] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(234), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2734] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(177), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2765] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(241), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2796] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(244), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2827] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(181), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2858] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(182), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2889] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ ACTIONS(293), 1,
+ anon_sym_GT,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(76), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(291), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2920] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(271), 1,
+ anon_sym_LBRACK,
+ STATE(217), 1,
+ sym_type_expression,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ STATE(69), 4,
+ sym_generic_arguments,
+ sym__type_item,
+ sym__tuple_type,
+ aux_sym_generic_arguments_repeat1,
+ ACTIONS(273), 6,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_DOT_DOT,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [2951] = 9,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(295), 1,
+ anon_sym_LBRACE,
+ ACTIONS(297), 1,
+ anon_sym_RBRACE,
+ ACTIONS(299), 1,
+ anon_sym_LBRACK,
+ ACTIONS(301), 1,
+ anon_sym_LPAREN,
+ ACTIONS(303), 1,
+ anon_sym_SLASH,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(305), 2,
+ sym__opaque_token,
+ sym_string,
+ STATE(68), 5,
+ sym_block,
+ sym__paren_group,
+ sym__bracket_group,
+ sym__opaque,
+ aux_sym_block_repeat1,
+ [2985] = 9,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(295), 1,
+ anon_sym_LBRACE,
+ ACTIONS(299), 1,
+ anon_sym_LBRACK,
+ ACTIONS(301), 1,
+ anon_sym_LPAREN,
+ ACTIONS(307), 1,
+ anon_sym_RBRACK,
+ ACTIONS(309), 1,
+ anon_sym_SLASH,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(311), 2,
+ sym__opaque_token,
+ sym_string,
+ STATE(104), 5,
+ sym_block,
+ sym__paren_group,
+ sym__bracket_group,
+ sym__opaque,
+ aux_sym_block_repeat1,
+ [3019] = 9,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(295), 1,
+ anon_sym_LBRACE,
+ ACTIONS(299), 1,
+ anon_sym_LBRACK,
+ ACTIONS(301), 1,
+ anon_sym_LPAREN,
+ ACTIONS(303), 1,
+ anon_sym_SLASH,
+ ACTIONS(313), 1,
+ anon_sym_RBRACE,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(305), 2,
+ sym__opaque_token,
+ sym_string,
+ STATE(68), 5,
+ sym_block,
+ sym__paren_group,
+ sym__bracket_group,
+ sym__opaque,
+ aux_sym_block_repeat1,
+ [3053] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(315), 12,
+ anon_sym_SEMI,
+ anon_sym_LBRACE,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_LT,
+ anon_sym_GT,
+ anon_sym_DOT_DOT,
+ anon_sym_LBRACK,
+ anon_sym_RBRACK,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [3075] = 9,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(295), 1,
+ anon_sym_LBRACE,
+ ACTIONS(299), 1,
+ anon_sym_LBRACK,
+ ACTIONS(301), 1,
+ anon_sym_LPAREN,
+ ACTIONS(317), 1,
+ anon_sym_RBRACE,
+ ACTIONS(319), 1,
+ anon_sym_SLASH,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(321), 2,
+ sym__opaque_token,
+ sym_string,
+ STATE(98), 5,
+ sym_block,
+ sym__paren_group,
+ sym__bracket_group,
+ sym__opaque,
+ aux_sym_block_repeat1,
+ [3109] = 9,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(295), 1,
+ anon_sym_LBRACE,
+ ACTIONS(299), 1,
+ anon_sym_LBRACK,
+ ACTIONS(301), 1,
+ anon_sym_LPAREN,
+ ACTIONS(323), 1,
+ anon_sym_RPAREN,
+ ACTIONS(325), 1,
+ anon_sym_SLASH,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(327), 2,
+ sym__opaque_token,
+ sym_string,
+ STATE(106), 5,
+ sym_block,
+ sym__paren_group,
+ sym__bracket_group,
+ sym__opaque,
+ aux_sym_block_repeat1,
+ [3143] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(329), 12,
+ anon_sym_SEMI,
+ anon_sym_LBRACE,
+ anon_sym_COMMA,
+ anon_sym_COLON,
+ anon_sym_LT,
+ anon_sym_GT,
+ anon_sym_DOT_DOT,
+ anon_sym_LBRACK,
+ anon_sym_RBRACK,
+ sym_identifier,
+ sym_number,
+ sym_string,
+ [3165] = 9,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(295), 1,
+ anon_sym_LBRACE,
+ ACTIONS(299), 1,
+ anon_sym_LBRACK,
+ ACTIONS(301), 1,
+ anon_sym_LPAREN,
+ ACTIONS(331), 1,
+ anon_sym_RPAREN,
+ ACTIONS(333), 1,
+ anon_sym_SLASH,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(335), 2,
+ sym__opaque_token,
+ sym_string,
+ STATE(107), 5,
+ sym_block,
+ sym__paren_group,
+ sym__bracket_group,
+ sym__opaque,
+ aux_sym_block_repeat1,
+ [3199] = 9,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(295), 1,
+ anon_sym_LBRACE,
+ ACTIONS(299), 1,
+ anon_sym_LBRACK,
+ ACTIONS(301), 1,
+ anon_sym_LPAREN,
+ ACTIONS(303), 1,
+ anon_sym_SLASH,
+ ACTIONS(337), 1,
+ anon_sym_RBRACK,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(305), 2,
+ sym__opaque_token,
+ sym_string,
+ STATE(68), 5,
+ sym_block,
+ sym__paren_group,
+ sym__bracket_group,
+ sym__opaque,
+ aux_sym_block_repeat1,
+ [3233] = 14,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(339), 1,
+ anon_sym_LBRACE,
+ ACTIONS(341), 1,
+ anon_sym_module,
+ ACTIONS(343), 1,
+ anon_sym_struct,
+ ACTIONS(345), 1,
+ anon_sym_enum,
+ ACTIONS(347), 1,
+ anon_sym_contract,
+ ACTIONS(349), 1,
+ anon_sym_new,
+ ACTIONS(351), 1,
+ anon_sym_type,
+ ACTIONS(353), 1,
+ anon_sym_sealed,
+ ACTIONS(355), 1,
+ anon_sym_ledger,
+ ACTIONS(357), 1,
+ anon_sym_witness,
+ ACTIONS(359), 1,
+ anon_sym_pure,
+ ACTIONS(361), 1,
+ anon_sym_circuit,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3277] = 9,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(295), 1,
+ anon_sym_LBRACE,
+ ACTIONS(299), 1,
+ anon_sym_LBRACK,
+ ACTIONS(301), 1,
+ anon_sym_LPAREN,
+ ACTIONS(303), 1,
+ anon_sym_SLASH,
+ ACTIONS(363), 1,
+ anon_sym_RPAREN,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(305), 2,
+ sym__opaque_token,
+ sym_string,
+ STATE(68), 5,
+ sym_block,
+ sym__paren_group,
+ sym__bracket_group,
+ sym__opaque,
+ aux_sym_block_repeat1,
+ [3311] = 9,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(295), 1,
+ anon_sym_LBRACE,
+ ACTIONS(299), 1,
+ anon_sym_LBRACK,
+ ACTIONS(301), 1,
+ anon_sym_LPAREN,
+ ACTIONS(303), 1,
+ anon_sym_SLASH,
+ ACTIONS(365), 1,
+ anon_sym_RPAREN,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(305), 2,
+ sym__opaque_token,
+ sym_string,
+ STATE(68), 5,
+ sym_block,
+ sym__paren_group,
+ sym__bracket_group,
+ sym__opaque,
+ aux_sym_block_repeat1,
+ [3345] = 9,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(295), 1,
+ anon_sym_LBRACE,
+ ACTIONS(299), 1,
+ anon_sym_LBRACK,
+ ACTIONS(301), 1,
+ anon_sym_LPAREN,
+ ACTIONS(367), 1,
+ anon_sym_RBRACE,
+ ACTIONS(369), 1,
+ anon_sym_SLASH,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(371), 2,
+ sym__opaque_token,
+ sym_string,
+ STATE(96), 5,
+ sym_block,
+ sym__paren_group,
+ sym__bracket_group,
+ sym__opaque,
+ aux_sym_block_repeat1,
+ [3379] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(375), 1,
+ anon_sym_SLASH,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(373), 8,
+ anon_sym_LBRACE,
+ anon_sym_RBRACE,
+ anon_sym_LBRACK,
+ anon_sym_RBRACK,
+ anon_sym_LPAREN,
+ anon_sym_RPAREN,
+ sym__opaque_token,
+ sym_string,
+ [3400] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(379), 1,
+ anon_sym_SLASH,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(377), 8,
+ anon_sym_LBRACE,
+ anon_sym_RBRACE,
+ anon_sym_LBRACK,
+ anon_sym_RBRACK,
+ anon_sym_LPAREN,
+ anon_sym_RPAREN,
+ sym__opaque_token,
+ sym_string,
+ [3421] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(383), 1,
+ anon_sym_SLASH,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(381), 8,
+ anon_sym_LBRACE,
+ anon_sym_RBRACE,
+ anon_sym_LBRACK,
+ anon_sym_RBRACK,
+ anon_sym_LPAREN,
+ anon_sym_RPAREN,
+ sym__opaque_token,
+ sym_string,
+ [3442] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(387), 1,
+ anon_sym_SLASH,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(385), 8,
+ anon_sym_LBRACE,
+ anon_sym_RBRACE,
+ anon_sym_LBRACK,
+ anon_sym_RBRACK,
+ anon_sym_LPAREN,
+ anon_sym_RPAREN,
+ sym__opaque_token,
+ sym_string,
+ [3463] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(389), 1,
+ anon_sym_SLASH,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(133), 8,
+ anon_sym_LBRACE,
+ anon_sym_RBRACE,
+ anon_sym_LBRACK,
+ anon_sym_RBRACK,
+ anon_sym_LPAREN,
+ anon_sym_RPAREN,
+ sym__opaque_token,
+ sym_string,
+ [3484] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(391), 1,
+ anon_sym_SLASH,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(143), 8,
+ anon_sym_LBRACE,
+ anon_sym_RBRACE,
+ anon_sym_LBRACK,
+ anon_sym_RBRACK,
+ anon_sym_LPAREN,
+ anon_sym_RPAREN,
+ sym__opaque_token,
+ sym_string,
+ [3505] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(393), 1,
+ anon_sym_SEMI,
+ ACTIONS(395), 1,
+ anon_sym_prefix,
+ STATE(148), 1,
+ sym_generic_arguments,
+ STATE(237), 1,
+ sym_import_prefix,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3528] = 7,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(269), 1,
+ anon_sym_LT,
+ ACTIONS(395), 1,
+ anon_sym_prefix,
+ ACTIONS(397), 1,
+ anon_sym_SEMI,
+ STATE(140), 1,
+ sym_generic_arguments,
+ STATE(200), 1,
+ sym_import_prefix,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3551] = 6,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(399), 1,
+ anon_sym_LBRACE,
+ ACTIONS(401), 1,
+ anon_sym_LT,
+ STATE(11), 1,
+ sym_block,
+ STATE(170), 1,
+ sym_generic_parameters,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3571] = 6,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(399), 1,
+ anon_sym_LBRACE,
+ ACTIONS(401), 1,
+ anon_sym_LT,
+ STATE(7), 1,
+ sym_block,
+ STATE(168), 1,
+ sym_generic_parameters,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3591] = 6,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(403), 1,
+ sym_identifier,
+ ACTIONS(405), 1,
+ anon_sym_GT,
+ ACTIONS(407), 1,
+ anon_sym_POUND,
+ STATE(173), 1,
+ sym_generic_parameter,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3611] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(411), 1,
+ anon_sym_LBRACE,
+ STATE(158), 1,
+ sym_import_selection,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(409), 2,
+ sym_identifier,
+ sym_string,
+ [3629] = 6,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(401), 1,
+ anon_sym_LT,
+ ACTIONS(413), 1,
+ anon_sym_LPAREN,
+ STATE(159), 1,
+ sym_generic_parameters,
+ STATE(211), 1,
+ sym_parameter_list,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3649] = 6,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(401), 1,
+ anon_sym_LT,
+ ACTIONS(415), 1,
+ anon_sym_LBRACE,
+ STATE(41), 1,
+ sym_module_body,
+ STATE(191), 1,
+ sym_generic_parameters,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3669] = 6,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(401), 1,
+ anon_sym_LT,
+ ACTIONS(415), 1,
+ anon_sym_LBRACE,
+ STATE(42), 1,
+ sym_module_body,
+ STATE(189), 1,
+ sym_generic_parameters,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3689] = 6,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(403), 1,
+ sym_identifier,
+ ACTIONS(407), 1,
+ anon_sym_POUND,
+ ACTIONS(417), 1,
+ anon_sym_GT,
+ STATE(132), 1,
+ sym_generic_parameter,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3709] = 6,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(401), 1,
+ anon_sym_LT,
+ ACTIONS(413), 1,
+ anon_sym_LPAREN,
+ STATE(188), 1,
+ sym_generic_parameters,
+ STATE(260), 1,
+ sym_parameter_list,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3729] = 6,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(401), 1,
+ anon_sym_LT,
+ ACTIONS(413), 1,
+ anon_sym_LPAREN,
+ STATE(180), 1,
+ sym_generic_parameters,
+ STATE(203), 1,
+ sym_parameter_list,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3749] = 6,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(403), 1,
+ sym_identifier,
+ ACTIONS(407), 1,
+ anon_sym_POUND,
+ ACTIONS(419), 1,
+ anon_sym_GT,
+ STATE(173), 1,
+ sym_generic_parameter,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3769] = 6,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(401), 1,
+ anon_sym_LT,
+ ACTIONS(413), 1,
+ anon_sym_LPAREN,
+ STATE(155), 1,
+ sym_generic_parameters,
+ STATE(259), 1,
+ sym_parameter_list,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3789] = 6,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(401), 1,
+ anon_sym_LT,
+ ACTIONS(413), 1,
+ anon_sym_LPAREN,
+ STATE(190), 1,
+ sym_generic_parameters,
+ STATE(263), 1,
+ sym_parameter_list,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3809] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(421), 3,
+ anon_sym_LBRACE,
+ anon_sym_EQ,
+ anon_sym_LPAREN,
+ [3822] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(423), 3,
+ anon_sym_LBRACE,
+ anon_sym_EQ,
+ anon_sym_LPAREN,
+ [3835] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(425), 1,
+ anon_sym_COMMA,
+ ACTIONS(427), 1,
+ anon_sym_GT,
+ STATE(139), 1,
+ aux_sym_generic_parameters_repeat1,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3852] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(429), 1,
+ sym_identifier,
+ ACTIONS(431), 1,
+ anon_sym_RBRACE,
+ STATE(167), 1,
+ sym_import_element,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3869] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(433), 1,
+ anon_sym_COMMA,
+ ACTIONS(436), 1,
+ anon_sym_RBRACE,
+ STATE(134), 1,
+ aux_sym_import_selection_repeat1,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3886] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(438), 1,
+ anon_sym_COMMA,
+ ACTIONS(441), 1,
+ anon_sym_RBRACE,
+ STATE(135), 1,
+ aux_sym_export_declaration_repeat1,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3903] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(401), 1,
+ anon_sym_LT,
+ ACTIONS(443), 1,
+ anon_sym_EQ,
+ STATE(250), 1,
+ sym_generic_parameters,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3920] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(429), 1,
+ sym_identifier,
+ ACTIONS(445), 1,
+ anon_sym_RBRACE,
+ STATE(167), 1,
+ sym_import_element,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3937] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(445), 1,
+ anon_sym_RBRACE,
+ ACTIONS(447), 1,
+ anon_sym_COMMA,
+ STATE(134), 1,
+ aux_sym_import_selection_repeat1,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3954] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(419), 1,
+ anon_sym_GT,
+ ACTIONS(449), 1,
+ anon_sym_COMMA,
+ STATE(146), 1,
+ aux_sym_generic_parameters_repeat1,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3971] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(395), 1,
+ anon_sym_prefix,
+ ACTIONS(451), 1,
+ anon_sym_SEMI,
+ STATE(219), 1,
+ sym_import_prefix,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [3988] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(453), 1,
+ anon_sym_COMMA,
+ ACTIONS(455), 1,
+ anon_sym_RBRACE,
+ STATE(135), 1,
+ aux_sym_export_declaration_repeat1,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4005] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(459), 1,
+ anon_sym_as,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(457), 2,
+ anon_sym_COMMA,
+ anon_sym_RBRACE,
+ [4020] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(461), 1,
+ anon_sym_COMMA,
+ ACTIONS(463), 1,
+ anon_sym_RBRACE,
+ STATE(138), 1,
+ aux_sym_import_selection_repeat1,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4037] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(465), 3,
+ anon_sym_LBRACE,
+ anon_sym_EQ,
+ anon_sym_LPAREN,
+ [4050] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(429), 1,
+ sym_identifier,
+ ACTIONS(467), 1,
+ anon_sym_RBRACE,
+ STATE(143), 1,
+ sym_import_element,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4067] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(469), 1,
+ anon_sym_COMMA,
+ ACTIONS(472), 1,
+ anon_sym_GT,
+ STATE(146), 1,
+ aux_sym_generic_parameters_repeat1,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4084] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(401), 1,
+ anon_sym_LT,
+ ACTIONS(474), 1,
+ anon_sym_EQ,
+ STATE(243), 1,
+ sym_generic_parameters,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4101] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(395), 1,
+ anon_sym_prefix,
+ ACTIONS(476), 1,
+ anon_sym_SEMI,
+ STATE(197), 1,
+ sym_import_prefix,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4118] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(478), 1,
+ anon_sym_COMMA,
+ ACTIONS(480), 1,
+ anon_sym_RBRACE,
+ STATE(141), 1,
+ aux_sym_export_declaration_repeat1,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4135] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(403), 1,
+ sym_identifier,
+ ACTIONS(407), 1,
+ anon_sym_POUND,
+ STATE(173), 1,
+ sym_generic_parameter,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4152] = 5,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(401), 1,
+ anon_sym_LT,
+ ACTIONS(482), 1,
+ anon_sym_EQ,
+ STATE(245), 1,
+ sym_generic_parameters,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4169] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(484), 3,
+ anon_sym_LBRACE,
+ anon_sym_EQ,
+ anon_sym_LPAREN,
+ [4182] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(486), 2,
+ sym_identifier,
+ sym_string,
+ [4194] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(488), 2,
+ anon_sym_LBRACE,
+ anon_sym_COLON,
+ [4206] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(413), 1,
+ anon_sym_LPAREN,
+ STATE(225), 1,
+ sym_parameter_list,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4220] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(490), 2,
+ anon_sym_COMMA,
+ anon_sym_GT,
+ [4232] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(413), 1,
+ anon_sym_LPAREN,
+ STATE(160), 1,
+ sym_parameter_list,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4246] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(492), 2,
+ sym_identifier,
+ sym_string,
+ [4258] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(413), 1,
+ anon_sym_LPAREN,
+ STATE(194), 1,
+ sym_parameter_list,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4272] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(399), 1,
+ anon_sym_LBRACE,
+ STATE(22), 1,
+ sym_block,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4286] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(494), 2,
+ anon_sym_LBRACE,
+ anon_sym_COLON,
+ [4298] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(455), 1,
+ anon_sym_RBRACE,
+ ACTIONS(496), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4312] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(399), 1,
+ anon_sym_LBRACE,
+ STATE(53), 1,
+ sym_block,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4326] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(498), 2,
+ sym_identifier,
+ sym_string,
+ [4338] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(429), 1,
+ sym_identifier,
+ STATE(167), 1,
+ sym_import_element,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4352] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(500), 2,
+ anon_sym_COMMA,
+ anon_sym_RBRACE,
+ [4364] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(436), 2,
+ anon_sym_COMMA,
+ anon_sym_RBRACE,
+ [4376] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(399), 1,
+ anon_sym_LBRACE,
+ STATE(14), 1,
+ sym_block,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4390] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(502), 1,
+ sym_identifier,
+ ACTIONS(504), 1,
+ anon_sym_implements,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4404] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(399), 1,
+ anon_sym_LBRACE,
+ STATE(17), 1,
+ sym_block,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4418] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(399), 1,
+ anon_sym_LBRACE,
+ STATE(58), 1,
+ sym_block,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4432] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(506), 2,
+ anon_sym_COMMA,
+ anon_sym_GT,
+ [4444] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(472), 2,
+ anon_sym_COMMA,
+ anon_sym_GT,
+ [4456] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(508), 1,
+ sym_identifier,
+ ACTIONS(510), 1,
+ anon_sym_RBRACE,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4470] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(441), 2,
+ anon_sym_COMMA,
+ anon_sym_RBRACE,
+ [4482] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(512), 2,
+ sym_identifier,
+ sym_string,
+ [4494] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(399), 1,
+ anon_sym_LBRACE,
+ STATE(60), 1,
+ sym_block,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4508] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ ACTIONS(514), 2,
+ sym_identifier,
+ sym_string,
+ [4520] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(496), 1,
+ sym_identifier,
+ ACTIONS(516), 1,
+ anon_sym_RBRACE,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4534] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(413), 1,
+ anon_sym_LPAREN,
+ STATE(230), 1,
+ sym_parameter_list,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4548] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(399), 1,
+ anon_sym_LBRACE,
+ STATE(63), 1,
+ sym_block,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4562] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(399), 1,
+ anon_sym_LBRACE,
+ STATE(64), 1,
+ sym_block,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4576] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(399), 1,
+ anon_sym_LBRACE,
+ STATE(8), 1,
+ sym_block,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4590] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(399), 1,
+ anon_sym_LBRACE,
+ STATE(12), 1,
+ sym_block,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4604] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(399), 1,
+ anon_sym_LBRACE,
+ STATE(13), 1,
+ sym_block,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4618] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(399), 1,
+ anon_sym_LBRACE,
+ STATE(9), 1,
+ sym_block,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4632] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(399), 1,
+ anon_sym_LBRACE,
+ STATE(65), 1,
+ sym_block,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4646] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(413), 1,
+ anon_sym_LPAREN,
+ STATE(253), 1,
+ sym_parameter_list,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4660] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(415), 1,
+ anon_sym_LBRACE,
+ STATE(23), 1,
+ sym_module_body,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4674] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(413), 1,
+ anon_sym_LPAREN,
+ STATE(261), 1,
+ sym_parameter_list,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4688] = 4,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(415), 1,
+ anon_sym_LBRACE,
+ STATE(35), 1,
+ sym_module_body,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4702] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(518), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4713] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(520), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4724] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(522), 1,
+ anon_sym_COLON,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4735] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(524), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4746] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(526), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4757] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(528), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4768] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(530), 1,
+ anon_sym_from,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4779] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(361), 1,
+ anon_sym_circuit,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4790] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(451), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4801] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(532), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4812] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(534), 1,
+ sym_string,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4823] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(536), 1,
+ anon_sym_COLON,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4834] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(538), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4845] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(540), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4856] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(542), 1,
+ anon_sym_type,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4867] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(544), 1,
+ ts_builtin_sym_end,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4878] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(546), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4889] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(548), 1,
+ anon_sym_ledger,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4900] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(550), 1,
+ anon_sym_from,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4911] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(552), 1,
+ anon_sym_COLON,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4922] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(554), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4933] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(556), 1,
+ anon_sym_circuit,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4944] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(558), 1,
+ anon_sym_from,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4955] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(496), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4966] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(560), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4977] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(562), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4988] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(564), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [4999] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(566), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5010] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(568), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5021] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(570), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5032] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(572), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5043] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(574), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5054] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(576), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5065] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(578), 1,
+ anon_sym_COLON,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5076] = 2,
+ ACTIONS(580), 1,
+ sym_version_constraint,
+ ACTIONS(5), 3,
+ sym_doc_comment,
+ sym_block_comment,
+ sym_line_comment,
+ [5085] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(582), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5096] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(584), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5107] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(586), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5118] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(588), 1,
+ anon_sym_COLON,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5129] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(590), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5140] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(592), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5151] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(594), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5162] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(596), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5173] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(598), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5184] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(351), 1,
+ anon_sym_type,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5195] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(476), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5206] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(600), 1,
+ anon_sym_COLON,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5217] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(602), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5228] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(604), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5239] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(606), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5250] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(608), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5261] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(610), 1,
+ anon_sym_EQ,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5272] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(612), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5283] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(614), 1,
+ anon_sym_EQ,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5294] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(616), 1,
+ anon_sym_COLON,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5305] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(618), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5316] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(620), 1,
+ anon_sym_from,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5327] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(355), 1,
+ anon_sym_ledger,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5338] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(622), 1,
+ anon_sym_EQ,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5349] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(624), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5360] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(626), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5371] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(628), 1,
+ anon_sym_COLON,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5382] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(630), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5393] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(632), 1,
+ anon_sym_SEMI,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5404] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(634), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5415] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(636), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5426] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(638), 1,
+ anon_sym_COLON,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5437] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(640), 1,
+ anon_sym_COLON,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5448] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(642), 1,
+ anon_sym_COLON,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5459] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(644), 1,
+ anon_sym_COLON,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5470] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(646), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5481] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(648), 1,
+ anon_sym_COLON,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+ [5492] = 3,
+ ACTIONS(5), 1,
+ sym_block_comment,
+ ACTIONS(650), 1,
+ sym_identifier,
+ ACTIONS(3), 2,
+ sym_doc_comment,
+ sym_line_comment,
+};
+
+static const uint32_t ts_small_parse_table_map[] = {
+ [SMALL_STATE(2)] = 0,
+ [SMALL_STATE(3)] = 78,
+ [SMALL_STATE(4)] = 155,
+ [SMALL_STATE(5)] = 232,
+ [SMALL_STATE(6)] = 309,
+ [SMALL_STATE(7)] = 340,
+ [SMALL_STATE(8)] = 371,
+ [SMALL_STATE(9)] = 402,
+ [SMALL_STATE(10)] = 433,
+ [SMALL_STATE(11)] = 464,
+ [SMALL_STATE(12)] = 495,
+ [SMALL_STATE(13)] = 526,
+ [SMALL_STATE(14)] = 557,
+ [SMALL_STATE(15)] = 588,
+ [SMALL_STATE(16)] = 617,
+ [SMALL_STATE(17)] = 648,
+ [SMALL_STATE(18)] = 679,
+ [SMALL_STATE(19)] = 708,
+ [SMALL_STATE(20)] = 739,
+ [SMALL_STATE(21)] = 767,
+ [SMALL_STATE(22)] = 795,
+ [SMALL_STATE(23)] = 823,
+ [SMALL_STATE(24)] = 851,
+ [SMALL_STATE(25)] = 879,
+ [SMALL_STATE(26)] = 907,
+ [SMALL_STATE(27)] = 935,
+ [SMALL_STATE(28)] = 963,
+ [SMALL_STATE(29)] = 991,
+ [SMALL_STATE(30)] = 1019,
+ [SMALL_STATE(31)] = 1047,
+ [SMALL_STATE(32)] = 1075,
+ [SMALL_STATE(33)] = 1103,
+ [SMALL_STATE(34)] = 1131,
+ [SMALL_STATE(35)] = 1159,
+ [SMALL_STATE(36)] = 1187,
+ [SMALL_STATE(37)] = 1215,
+ [SMALL_STATE(38)] = 1243,
+ [SMALL_STATE(39)] = 1271,
+ [SMALL_STATE(40)] = 1299,
+ [SMALL_STATE(41)] = 1327,
+ [SMALL_STATE(42)] = 1355,
+ [SMALL_STATE(43)] = 1383,
+ [SMALL_STATE(44)] = 1411,
+ [SMALL_STATE(45)] = 1439,
+ [SMALL_STATE(46)] = 1467,
+ [SMALL_STATE(47)] = 1495,
+ [SMALL_STATE(48)] = 1523,
+ [SMALL_STATE(49)] = 1551,
+ [SMALL_STATE(50)] = 1579,
+ [SMALL_STATE(51)] = 1607,
+ [SMALL_STATE(52)] = 1635,
+ [SMALL_STATE(53)] = 1663,
+ [SMALL_STATE(54)] = 1691,
+ [SMALL_STATE(55)] = 1719,
+ [SMALL_STATE(56)] = 1747,
+ [SMALL_STATE(57)] = 1775,
+ [SMALL_STATE(58)] = 1803,
+ [SMALL_STATE(59)] = 1831,
+ [SMALL_STATE(60)] = 1859,
+ [SMALL_STATE(61)] = 1887,
+ [SMALL_STATE(62)] = 1915,
+ [SMALL_STATE(63)] = 1943,
+ [SMALL_STATE(64)] = 1971,
+ [SMALL_STATE(65)] = 1999,
+ [SMALL_STATE(66)] = 2027,
+ [SMALL_STATE(67)] = 2055,
+ [SMALL_STATE(68)] = 2089,
+ [SMALL_STATE(69)] = 2125,
+ [SMALL_STATE(70)] = 2157,
+ [SMALL_STATE(71)] = 2188,
+ [SMALL_STATE(72)] = 2219,
+ [SMALL_STATE(73)] = 2250,
+ [SMALL_STATE(74)] = 2281,
+ [SMALL_STATE(75)] = 2312,
+ [SMALL_STATE(76)] = 2337,
+ [SMALL_STATE(77)] = 2368,
+ [SMALL_STATE(78)] = 2399,
+ [SMALL_STATE(79)] = 2430,
+ [SMALL_STATE(80)] = 2461,
+ [SMALL_STATE(81)] = 2492,
+ [SMALL_STATE(82)] = 2523,
+ [SMALL_STATE(83)] = 2548,
+ [SMALL_STATE(84)] = 2579,
+ [SMALL_STATE(85)] = 2610,
+ [SMALL_STATE(86)] = 2641,
+ [SMALL_STATE(87)] = 2672,
+ [SMALL_STATE(88)] = 2703,
+ [SMALL_STATE(89)] = 2734,
+ [SMALL_STATE(90)] = 2765,
+ [SMALL_STATE(91)] = 2796,
+ [SMALL_STATE(92)] = 2827,
+ [SMALL_STATE(93)] = 2858,
+ [SMALL_STATE(94)] = 2889,
+ [SMALL_STATE(95)] = 2920,
+ [SMALL_STATE(96)] = 2951,
+ [SMALL_STATE(97)] = 2985,
+ [SMALL_STATE(98)] = 3019,
+ [SMALL_STATE(99)] = 3053,
+ [SMALL_STATE(100)] = 3075,
+ [SMALL_STATE(101)] = 3109,
+ [SMALL_STATE(102)] = 3143,
+ [SMALL_STATE(103)] = 3165,
+ [SMALL_STATE(104)] = 3199,
+ [SMALL_STATE(105)] = 3233,
+ [SMALL_STATE(106)] = 3277,
+ [SMALL_STATE(107)] = 3311,
+ [SMALL_STATE(108)] = 3345,
+ [SMALL_STATE(109)] = 3379,
+ [SMALL_STATE(110)] = 3400,
+ [SMALL_STATE(111)] = 3421,
+ [SMALL_STATE(112)] = 3442,
+ [SMALL_STATE(113)] = 3463,
+ [SMALL_STATE(114)] = 3484,
+ [SMALL_STATE(115)] = 3505,
+ [SMALL_STATE(116)] = 3528,
+ [SMALL_STATE(117)] = 3551,
+ [SMALL_STATE(118)] = 3571,
+ [SMALL_STATE(119)] = 3591,
+ [SMALL_STATE(120)] = 3611,
+ [SMALL_STATE(121)] = 3629,
+ [SMALL_STATE(122)] = 3649,
+ [SMALL_STATE(123)] = 3669,
+ [SMALL_STATE(124)] = 3689,
+ [SMALL_STATE(125)] = 3709,
+ [SMALL_STATE(126)] = 3729,
+ [SMALL_STATE(127)] = 3749,
+ [SMALL_STATE(128)] = 3769,
+ [SMALL_STATE(129)] = 3789,
+ [SMALL_STATE(130)] = 3809,
+ [SMALL_STATE(131)] = 3822,
+ [SMALL_STATE(132)] = 3835,
+ [SMALL_STATE(133)] = 3852,
+ [SMALL_STATE(134)] = 3869,
+ [SMALL_STATE(135)] = 3886,
+ [SMALL_STATE(136)] = 3903,
+ [SMALL_STATE(137)] = 3920,
+ [SMALL_STATE(138)] = 3937,
+ [SMALL_STATE(139)] = 3954,
+ [SMALL_STATE(140)] = 3971,
+ [SMALL_STATE(141)] = 3988,
+ [SMALL_STATE(142)] = 4005,
+ [SMALL_STATE(143)] = 4020,
+ [SMALL_STATE(144)] = 4037,
+ [SMALL_STATE(145)] = 4050,
+ [SMALL_STATE(146)] = 4067,
+ [SMALL_STATE(147)] = 4084,
+ [SMALL_STATE(148)] = 4101,
+ [SMALL_STATE(149)] = 4118,
+ [SMALL_STATE(150)] = 4135,
+ [SMALL_STATE(151)] = 4152,
+ [SMALL_STATE(152)] = 4169,
+ [SMALL_STATE(153)] = 4182,
+ [SMALL_STATE(154)] = 4194,
+ [SMALL_STATE(155)] = 4206,
+ [SMALL_STATE(156)] = 4220,
+ [SMALL_STATE(157)] = 4232,
+ [SMALL_STATE(158)] = 4246,
+ [SMALL_STATE(159)] = 4258,
+ [SMALL_STATE(160)] = 4272,
+ [SMALL_STATE(161)] = 4286,
+ [SMALL_STATE(162)] = 4298,
+ [SMALL_STATE(163)] = 4312,
+ [SMALL_STATE(164)] = 4326,
+ [SMALL_STATE(165)] = 4338,
+ [SMALL_STATE(166)] = 4352,
+ [SMALL_STATE(167)] = 4364,
+ [SMALL_STATE(168)] = 4376,
+ [SMALL_STATE(169)] = 4390,
+ [SMALL_STATE(170)] = 4404,
+ [SMALL_STATE(171)] = 4418,
+ [SMALL_STATE(172)] = 4432,
+ [SMALL_STATE(173)] = 4444,
+ [SMALL_STATE(174)] = 4456,
+ [SMALL_STATE(175)] = 4470,
+ [SMALL_STATE(176)] = 4482,
+ [SMALL_STATE(177)] = 4494,
+ [SMALL_STATE(178)] = 4508,
+ [SMALL_STATE(179)] = 4520,
+ [SMALL_STATE(180)] = 4534,
+ [SMALL_STATE(181)] = 4548,
+ [SMALL_STATE(182)] = 4562,
+ [SMALL_STATE(183)] = 4576,
+ [SMALL_STATE(184)] = 4590,
+ [SMALL_STATE(185)] = 4604,
+ [SMALL_STATE(186)] = 4618,
+ [SMALL_STATE(187)] = 4632,
+ [SMALL_STATE(188)] = 4646,
+ [SMALL_STATE(189)] = 4660,
+ [SMALL_STATE(190)] = 4674,
+ [SMALL_STATE(191)] = 4688,
+ [SMALL_STATE(192)] = 4702,
+ [SMALL_STATE(193)] = 4713,
+ [SMALL_STATE(194)] = 4724,
+ [SMALL_STATE(195)] = 4735,
+ [SMALL_STATE(196)] = 4746,
+ [SMALL_STATE(197)] = 4757,
+ [SMALL_STATE(198)] = 4768,
+ [SMALL_STATE(199)] = 4779,
+ [SMALL_STATE(200)] = 4790,
+ [SMALL_STATE(201)] = 4801,
+ [SMALL_STATE(202)] = 4812,
+ [SMALL_STATE(203)] = 4823,
+ [SMALL_STATE(204)] = 4834,
+ [SMALL_STATE(205)] = 4845,
+ [SMALL_STATE(206)] = 4856,
+ [SMALL_STATE(207)] = 4867,
+ [SMALL_STATE(208)] = 4878,
+ [SMALL_STATE(209)] = 4889,
+ [SMALL_STATE(210)] = 4900,
+ [SMALL_STATE(211)] = 4911,
+ [SMALL_STATE(212)] = 4922,
+ [SMALL_STATE(213)] = 4933,
+ [SMALL_STATE(214)] = 4944,
+ [SMALL_STATE(215)] = 4955,
+ [SMALL_STATE(216)] = 4966,
+ [SMALL_STATE(217)] = 4977,
+ [SMALL_STATE(218)] = 4988,
+ [SMALL_STATE(219)] = 4999,
+ [SMALL_STATE(220)] = 5010,
+ [SMALL_STATE(221)] = 5021,
+ [SMALL_STATE(222)] = 5032,
+ [SMALL_STATE(223)] = 5043,
+ [SMALL_STATE(224)] = 5054,
+ [SMALL_STATE(225)] = 5065,
+ [SMALL_STATE(226)] = 5076,
+ [SMALL_STATE(227)] = 5085,
+ [SMALL_STATE(228)] = 5096,
+ [SMALL_STATE(229)] = 5107,
+ [SMALL_STATE(230)] = 5118,
+ [SMALL_STATE(231)] = 5129,
+ [SMALL_STATE(232)] = 5140,
+ [SMALL_STATE(233)] = 5151,
+ [SMALL_STATE(234)] = 5162,
+ [SMALL_STATE(235)] = 5173,
+ [SMALL_STATE(236)] = 5184,
+ [SMALL_STATE(237)] = 5195,
+ [SMALL_STATE(238)] = 5206,
+ [SMALL_STATE(239)] = 5217,
+ [SMALL_STATE(240)] = 5228,
+ [SMALL_STATE(241)] = 5239,
+ [SMALL_STATE(242)] = 5250,
+ [SMALL_STATE(243)] = 5261,
+ [SMALL_STATE(244)] = 5272,
+ [SMALL_STATE(245)] = 5283,
+ [SMALL_STATE(246)] = 5294,
+ [SMALL_STATE(247)] = 5305,
+ [SMALL_STATE(248)] = 5316,
+ [SMALL_STATE(249)] = 5327,
+ [SMALL_STATE(250)] = 5338,
+ [SMALL_STATE(251)] = 5349,
+ [SMALL_STATE(252)] = 5360,
+ [SMALL_STATE(253)] = 5371,
+ [SMALL_STATE(254)] = 5382,
+ [SMALL_STATE(255)] = 5393,
+ [SMALL_STATE(256)] = 5404,
+ [SMALL_STATE(257)] = 5415,
+ [SMALL_STATE(258)] = 5426,
+ [SMALL_STATE(259)] = 5437,
+ [SMALL_STATE(260)] = 5448,
+ [SMALL_STATE(261)] = 5459,
+ [SMALL_STATE(262)] = 5470,
+ [SMALL_STATE(263)] = 5481,
+ [SMALL_STATE(264)] = 5492,
+};
+
+static const TSParseActionEntry ts_parse_actions[] = {
+ [0] = {.entry = {.count = 0, .reusable = false}},
+ [1] = {.entry = {.count = 1, .reusable = false}}, RECOVER(),
+ [3] = {.entry = {.count = 1, .reusable = true}}, SHIFT_EXTRA(),
+ [5] = {.entry = {.count = 1, .reusable = false}}, SHIFT_EXTRA(),
+ [7] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_source_file, 0, 0, 0),
+ [9] = {.entry = {.count = 1, .reusable = true}}, SHIFT(218),
+ [11] = {.entry = {.count = 1, .reusable = true}}, SHIFT(202),
+ [13] = {.entry = {.count = 1, .reusable = true}}, SHIFT(120),
+ [15] = {.entry = {.count = 1, .reusable = true}}, SHIFT(105),
+ [17] = {.entry = {.count = 1, .reusable = true}}, SHIFT(242),
+ [19] = {.entry = {.count = 1, .reusable = true}}, SHIFT(231),
+ [21] = {.entry = {.count = 1, .reusable = true}}, SHIFT(262),
+ [23] = {.entry = {.count = 1, .reusable = true}}, SHIFT(169),
+ [25] = {.entry = {.count = 1, .reusable = true}}, SHIFT(236),
+ [27] = {.entry = {.count = 1, .reusable = true}}, SHIFT(240),
+ [29] = {.entry = {.count = 1, .reusable = true}}, SHIFT(249),
+ [31] = {.entry = {.count = 1, .reusable = true}}, SHIFT(239),
+ [33] = {.entry = {.count = 1, .reusable = true}}, SHIFT(222),
+ [35] = {.entry = {.count = 1, .reusable = true}}, SHIFT(157),
+ [37] = {.entry = {.count = 1, .reusable = true}}, SHIFT(199),
+ [39] = {.entry = {.count = 1, .reusable = true}}, SHIFT(205),
+ [41] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0),
+ [43] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(218),
+ [46] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(202),
+ [49] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(120),
+ [52] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(105),
+ [55] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(242),
+ [58] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(231),
+ [61] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(262),
+ [64] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(169),
+ [67] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(236),
+ [70] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(240),
+ [73] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(249),
+ [76] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(239),
+ [79] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(222),
+ [82] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(157),
+ [85] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(199),
+ [88] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(205),
+ [91] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_source_file, 1, 0, 0),
+ [93] = {.entry = {.count = 1, .reusable = true}}, SHIFT(21),
+ [95] = {.entry = {.count = 1, .reusable = true}}, SHIFT(40),
+ [97] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_export_declaration, 3, 0, 0),
+ [99] = {.entry = {.count = 1, .reusable = true}}, SHIFT(33),
+ [101] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_declaration, 3, 0, 4),
+ [103] = {.entry = {.count = 1, .reusable = true}}, SHIFT(26),
+ [105] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_enum_declaration, 3, 0, 4),
+ [107] = {.entry = {.count = 1, .reusable = true}}, SHIFT(27),
+ [109] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_contract_declaration, 3, 0, 4),
+ [111] = {.entry = {.count = 1, .reusable = true}}, SHIFT(29),
+ [113] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_export_declaration, 4, 0, 0),
+ [115] = {.entry = {.count = 1, .reusable = true}}, SHIFT(34),
+ [117] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_declaration, 4, 0, 8),
+ [119] = {.entry = {.count = 1, .reusable = true}}, SHIFT(37),
+ [121] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_enum_declaration, 4, 0, 8),
+ [123] = {.entry = {.count = 1, .reusable = true}}, SHIFT(38),
+ [125] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_contract_declaration, 4, 0, 8),
+ [127] = {.entry = {.count = 1, .reusable = true}}, SHIFT(39),
+ [129] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_declaration, 4, 0, 9),
+ [131] = {.entry = {.count = 1, .reusable = true}}, SHIFT(66),
+ [133] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_block, 2, 0, 0),
+ [135] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_export_declaration, 5, 0, 0),
+ [137] = {.entry = {.count = 1, .reusable = true}}, SHIFT(46),
+ [139] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_declaration, 5, 0, 12),
+ [141] = {.entry = {.count = 1, .reusable = true}}, SHIFT(48),
+ [143] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_block, 3, 0, 0),
+ [145] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_export_declaration, 6, 0, 0),
+ [147] = {.entry = {.count = 1, .reusable = true}}, SHIFT(54),
+ [149] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_ledger_declaration, 7, 0, 22),
+ [151] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_module_body, 2, 0, 0),
+ [153] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_constructor_declaration, 3, 0, 5),
+ [155] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_module_declaration, 4, 0, 9),
+ [157] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_pragma_declaration, 4, 0, 3),
+ [159] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_declaration, 4, 0, 3),
+ [161] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_declaration, 4, 0, 4),
+ [163] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_enum_declaration, 4, 0, 4),
+ [165] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_implements_declaration, 4, 0, 10),
+ [167] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_contract_declaration, 4, 0, 4),
+ [169] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_declaration, 4, 0, 7),
+ [171] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_declaration, 5, 0, 3),
+ [173] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_declaration, 5, 0, 7),
+ [175] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_module_declaration, 5, 0, 12),
+ [177] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_include_declaration, 3, 0, 1),
+ [179] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_declaration, 5, 0, 8),
+ [181] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_enum_declaration, 5, 0, 8),
+ [183] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_contract_declaration, 5, 0, 8),
+ [185] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_module_body, 3, 0, 0),
+ [187] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_module_declaration, 4, 0, 8),
+ [189] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_module_declaration, 3, 0, 4),
+ [191] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_type_declaration, 5, 0, 13),
+ [193] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_ledger_declaration, 5, 0, 14),
+ [195] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_declaration, 6, 0, 7),
+ [197] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_declaration, 3, 0, 3),
+ [199] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_declaration, 6, 0, 12),
+ [201] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_type_declaration, 6, 0, 15),
+ [203] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_ledger_declaration, 6, 0, 16),
+ [205] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_type_declaration, 6, 0, 17),
+ [207] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_witness_declaration, 6, 0, 18),
+ [209] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_circuit_declaration, 6, 0, 19),
+ [211] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_export_declaration, 7, 0, 0),
+ [213] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_type_declaration, 7, 0, 20),
+ [215] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_type_declaration, 7, 0, 21),
+ [217] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_witness_declaration, 7, 0, 23),
+ [219] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_circuit_declaration, 7, 0, 24),
+ [221] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_witness_declaration, 7, 0, 25),
+ [223] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_circuit_declaration, 7, 0, 26),
+ [225] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_type_declaration, 8, 0, 27),
+ [227] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_witness_declaration, 8, 0, 28),
+ [229] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_circuit_declaration, 8, 0, 29),
+ [231] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_circuit_declaration, 8, 0, 30),
+ [233] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_circuit_declaration, 9, 0, 31),
+ [235] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_declaration, 5, 0, 9),
+ [237] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_generic_arguments_repeat1, 2, 0, 0), SHIFT_REPEAT(67),
+ [240] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_generic_arguments_repeat1, 2, 0, 0),
+ [242] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_generic_arguments_repeat1, 2, 0, 0), SHIFT_REPEAT(94),
+ [245] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_generic_arguments_repeat1, 2, 0, 0), SHIFT_REPEAT(72),
+ [248] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(108),
+ [251] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0),
+ [253] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(97),
+ [256] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(103),
+ [259] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(68),
+ [262] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(68),
+ [265] = {.entry = {.count = 1, .reusable = true}}, SHIFT(67),
+ [267] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_type_expression, 1, 0, 0),
+ [269] = {.entry = {.count = 1, .reusable = true}}, SHIFT(94),
+ [271] = {.entry = {.count = 1, .reusable = true}}, SHIFT(72),
+ [273] = {.entry = {.count = 1, .reusable = true}}, SHIFT(69),
+ [275] = {.entry = {.count = 1, .reusable = true}}, SHIFT(79),
+ [277] = {.entry = {.count = 1, .reusable = true}}, SHIFT(99),
+ [279] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_generic_arguments, 2, 0, 0),
+ [281] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_generic_arguments, 2, 0, 0),
+ [283] = {.entry = {.count = 1, .reusable = true}}, SHIFT(82),
+ [285] = {.entry = {.count = 1, .reusable = true}}, SHIFT(102),
+ [287] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_generic_arguments, 3, 0, 0),
+ [289] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_generic_arguments, 3, 0, 0),
+ [291] = {.entry = {.count = 1, .reusable = true}}, SHIFT(76),
+ [293] = {.entry = {.count = 1, .reusable = true}}, SHIFT(75),
+ [295] = {.entry = {.count = 1, .reusable = true}}, SHIFT(108),
+ [297] = {.entry = {.count = 1, .reusable = true}}, SHIFT(114),
+ [299] = {.entry = {.count = 1, .reusable = true}}, SHIFT(97),
+ [301] = {.entry = {.count = 1, .reusable = true}}, SHIFT(103),
+ [303] = {.entry = {.count = 1, .reusable = false}}, SHIFT(68),
+ [305] = {.entry = {.count = 1, .reusable = true}}, SHIFT(68),
+ [307] = {.entry = {.count = 1, .reusable = true}}, SHIFT(112),
+ [309] = {.entry = {.count = 1, .reusable = false}}, SHIFT(104),
+ [311] = {.entry = {.count = 1, .reusable = true}}, SHIFT(104),
+ [313] = {.entry = {.count = 1, .reusable = true}}, SHIFT(18),
+ [315] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__tuple_type, 2, 0, 0),
+ [317] = {.entry = {.count = 1, .reusable = true}}, SHIFT(15),
+ [319] = {.entry = {.count = 1, .reusable = false}}, SHIFT(98),
+ [321] = {.entry = {.count = 1, .reusable = true}}, SHIFT(98),
+ [323] = {.entry = {.count = 1, .reusable = true}}, SHIFT(154),
+ [325] = {.entry = {.count = 1, .reusable = false}}, SHIFT(106),
+ [327] = {.entry = {.count = 1, .reusable = true}}, SHIFT(106),
+ [329] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__tuple_type, 3, 0, 0),
+ [331] = {.entry = {.count = 1, .reusable = true}}, SHIFT(111),
+ [333] = {.entry = {.count = 1, .reusable = false}}, SHIFT(107),
+ [335] = {.entry = {.count = 1, .reusable = true}}, SHIFT(107),
+ [337] = {.entry = {.count = 1, .reusable = true}}, SHIFT(109),
+ [339] = {.entry = {.count = 1, .reusable = true}}, SHIFT(174),
+ [341] = {.entry = {.count = 1, .reusable = true}}, SHIFT(228),
+ [343] = {.entry = {.count = 1, .reusable = true}}, SHIFT(235),
+ [345] = {.entry = {.count = 1, .reusable = true}}, SHIFT(251),
+ [347] = {.entry = {.count = 1, .reusable = true}}, SHIFT(256),
+ [349] = {.entry = {.count = 1, .reusable = true}}, SHIFT(206),
+ [351] = {.entry = {.count = 1, .reusable = true}}, SHIFT(208),
+ [353] = {.entry = {.count = 1, .reusable = true}}, SHIFT(209),
+ [355] = {.entry = {.count = 1, .reusable = true}}, SHIFT(193),
+ [357] = {.entry = {.count = 1, .reusable = true}}, SHIFT(195),
+ [359] = {.entry = {.count = 1, .reusable = true}}, SHIFT(213),
+ [361] = {.entry = {.count = 1, .reusable = true}}, SHIFT(216),
+ [363] = {.entry = {.count = 1, .reusable = true}}, SHIFT(161),
+ [365] = {.entry = {.count = 1, .reusable = true}}, SHIFT(110),
+ [367] = {.entry = {.count = 1, .reusable = true}}, SHIFT(113),
+ [369] = {.entry = {.count = 1, .reusable = false}}, SHIFT(96),
+ [371] = {.entry = {.count = 1, .reusable = true}}, SHIFT(96),
+ [373] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__bracket_group, 3, 0, 0),
+ [375] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym__bracket_group, 3, 0, 0),
+ [377] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__paren_group, 3, 0, 0),
+ [379] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym__paren_group, 3, 0, 0),
+ [381] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__paren_group, 2, 0, 0),
+ [383] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym__paren_group, 2, 0, 0),
+ [385] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__bracket_group, 2, 0, 0),
+ [387] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym__bracket_group, 2, 0, 0),
+ [389] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_block, 2, 0, 0),
+ [391] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_block, 3, 0, 0),
+ [393] = {.entry = {.count = 1, .reusable = true}}, SHIFT(47),
+ [395] = {.entry = {.count = 1, .reusable = true}}, SHIFT(233),
+ [397] = {.entry = {.count = 1, .reusable = true}}, SHIFT(30),
+ [399] = {.entry = {.count = 1, .reusable = true}}, SHIFT(100),
+ [401] = {.entry = {.count = 1, .reusable = true}}, SHIFT(124),
+ [403] = {.entry = {.count = 1, .reusable = true}}, SHIFT(172),
+ [405] = {.entry = {.count = 1, .reusable = true}}, SHIFT(130),
+ [407] = {.entry = {.count = 1, .reusable = true}}, SHIFT(196),
+ [409] = {.entry = {.count = 1, .reusable = true}}, SHIFT(115),
+ [411] = {.entry = {.count = 1, .reusable = true}}, SHIFT(145),
+ [413] = {.entry = {.count = 1, .reusable = true}}, SHIFT(101),
+ [415] = {.entry = {.count = 1, .reusable = true}}, SHIFT(4),
+ [417] = {.entry = {.count = 1, .reusable = true}}, SHIFT(131),
+ [419] = {.entry = {.count = 1, .reusable = true}}, SHIFT(144),
+ [421] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_generic_parameters, 5, 0, 0),
+ [423] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_generic_parameters, 2, 0, 0),
+ [425] = {.entry = {.count = 1, .reusable = true}}, SHIFT(127),
+ [427] = {.entry = {.count = 1, .reusable = true}}, SHIFT(152),
+ [429] = {.entry = {.count = 1, .reusable = true}}, SHIFT(142),
+ [431] = {.entry = {.count = 1, .reusable = true}}, SHIFT(210),
+ [433] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_import_selection_repeat1, 2, 0, 0), SHIFT_REPEAT(165),
+ [436] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_import_selection_repeat1, 2, 0, 0),
+ [438] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_export_declaration_repeat1, 2, 0, 0), SHIFT_REPEAT(215),
+ [441] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_export_declaration_repeat1, 2, 0, 0),
+ [443] = {.entry = {.count = 1, .reusable = true}}, SHIFT(73),
+ [445] = {.entry = {.count = 1, .reusable = true}}, SHIFT(198),
+ [447] = {.entry = {.count = 1, .reusable = true}}, SHIFT(133),
+ [449] = {.entry = {.count = 1, .reusable = true}}, SHIFT(119),
+ [451] = {.entry = {.count = 1, .reusable = true}}, SHIFT(32),
+ [453] = {.entry = {.count = 1, .reusable = true}}, SHIFT(179),
+ [455] = {.entry = {.count = 1, .reusable = true}}, SHIFT(16),
+ [457] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_element, 1, 0, 2),
+ [459] = {.entry = {.count = 1, .reusable = true}}, SHIFT(264),
+ [461] = {.entry = {.count = 1, .reusable = true}}, SHIFT(137),
+ [463] = {.entry = {.count = 1, .reusable = true}}, SHIFT(248),
+ [465] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_generic_parameters, 4, 0, 0),
+ [467] = {.entry = {.count = 1, .reusable = true}}, SHIFT(214),
+ [469] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_generic_parameters_repeat1, 2, 0, 0), SHIFT_REPEAT(150),
+ [472] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_generic_parameters_repeat1, 2, 0, 0),
+ [474] = {.entry = {.count = 1, .reusable = true}}, SHIFT(95),
+ [476] = {.entry = {.count = 1, .reusable = true}}, SHIFT(25),
+ [478] = {.entry = {.count = 1, .reusable = true}}, SHIFT(162),
+ [480] = {.entry = {.count = 1, .reusable = true}}, SHIFT(10),
+ [482] = {.entry = {.count = 1, .reusable = true}}, SHIFT(77),
+ [484] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_generic_parameters, 3, 0, 0),
+ [486] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_selection, 4, 0, 0),
+ [488] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_parameter_list, 2, 0, 0),
+ [490] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_generic_parameter, 2, 0, 3),
+ [492] = {.entry = {.count = 1, .reusable = true}}, SHIFT(116),
+ [494] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_parameter_list, 3, 0, 0),
+ [496] = {.entry = {.count = 1, .reusable = true}}, SHIFT(175),
+ [498] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_selection, 5, 0, 0),
+ [500] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_element, 3, 0, 11),
+ [502] = {.entry = {.count = 1, .reusable = false}}, SHIFT(186),
+ [504] = {.entry = {.count = 1, .reusable = false}}, SHIFT(83),
+ [506] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_generic_parameter, 1, 0, 2),
+ [508] = {.entry = {.count = 1, .reusable = true}}, SHIFT(149),
+ [510] = {.entry = {.count = 1, .reusable = true}}, SHIFT(6),
+ [512] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_selection, 3, 0, 0),
+ [514] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_selection, 6, 0, 0),
+ [516] = {.entry = {.count = 1, .reusable = true}}, SHIFT(19),
+ [518] = {.entry = {.count = 1, .reusable = true}}, SHIFT(128),
+ [520] = {.entry = {.count = 1, .reusable = true}}, SHIFT(258),
+ [522] = {.entry = {.count = 1, .reusable = true}}, SHIFT(89),
+ [524] = {.entry = {.count = 1, .reusable = true}}, SHIFT(125),
+ [526] = {.entry = {.count = 1, .reusable = true}}, SHIFT(156),
+ [528] = {.entry = {.count = 1, .reusable = true}}, SHIFT(31),
+ [530] = {.entry = {.count = 1, .reusable = true}}, SHIFT(164),
+ [532] = {.entry = {.count = 1, .reusable = true}}, SHIFT(51),
+ [534] = {.entry = {.count = 1, .reusable = true}}, SHIFT(227),
+ [536] = {.entry = {.count = 1, .reusable = true}}, SHIFT(81),
+ [538] = {.entry = {.count = 1, .reusable = true}}, SHIFT(52),
+ [540] = {.entry = {.count = 1, .reusable = true}}, SHIFT(121),
+ [542] = {.entry = {.count = 1, .reusable = true}}, SHIFT(254),
+ [544] = {.entry = {.count = 1, .reusable = true}}, ACCEPT_INPUT(),
+ [546] = {.entry = {.count = 1, .reusable = true}}, SHIFT(151),
+ [548] = {.entry = {.count = 1, .reusable = true}}, SHIFT(257),
+ [550] = {.entry = {.count = 1, .reusable = true}}, SHIFT(178),
+ [552] = {.entry = {.count = 1, .reusable = true}}, SHIFT(70),
+ [554] = {.entry = {.count = 1, .reusable = true}}, SHIFT(24),
+ [556] = {.entry = {.count = 1, .reusable = true}}, SHIFT(192),
+ [558] = {.entry = {.count = 1, .reusable = true}}, SHIFT(176),
+ [560] = {.entry = {.count = 1, .reusable = true}}, SHIFT(129),
+ [562] = {.entry = {.count = 1, .reusable = true}}, SHIFT(55),
+ [564] = {.entry = {.count = 1, .reusable = true}}, SHIFT(226),
+ [566] = {.entry = {.count = 1, .reusable = true}}, SHIFT(45),
+ [568] = {.entry = {.count = 1, .reusable = true}}, SHIFT(56),
+ [570] = {.entry = {.count = 1, .reusable = true}}, SHIFT(20),
+ [572] = {.entry = {.count = 1, .reusable = true}}, SHIFT(126),
+ [574] = {.entry = {.count = 1, .reusable = true}}, SHIFT(43),
+ [576] = {.entry = {.count = 1, .reusable = true}}, SHIFT(57),
+ [578] = {.entry = {.count = 1, .reusable = true}}, SHIFT(71),
+ [580] = {.entry = {.count = 1, .reusable = false}}, SHIFT(212),
+ [582] = {.entry = {.count = 1, .reusable = true}}, SHIFT(36),
+ [584] = {.entry = {.count = 1, .reusable = true}}, SHIFT(122),
+ [586] = {.entry = {.count = 1, .reusable = true}}, SHIFT(44),
+ [588] = {.entry = {.count = 1, .reusable = true}}, SHIFT(88),
+ [590] = {.entry = {.count = 1, .reusable = true}}, SHIFT(118),
+ [592] = {.entry = {.count = 1, .reusable = true}}, SHIFT(28),
+ [594] = {.entry = {.count = 1, .reusable = true}}, SHIFT(255),
+ [596] = {.entry = {.count = 1, .reusable = true}}, SHIFT(59),
+ [598] = {.entry = {.count = 1, .reusable = true}}, SHIFT(117),
+ [600] = {.entry = {.count = 1, .reusable = true}}, SHIFT(74),
+ [602] = {.entry = {.count = 1, .reusable = true}}, SHIFT(238),
+ [604] = {.entry = {.count = 1, .reusable = true}}, SHIFT(136),
+ [606] = {.entry = {.count = 1, .reusable = true}}, SHIFT(61),
+ [608] = {.entry = {.count = 1, .reusable = true}}, SHIFT(123),
+ [610] = {.entry = {.count = 1, .reusable = true}}, SHIFT(90),
+ [612] = {.entry = {.count = 1, .reusable = true}}, SHIFT(62),
+ [614] = {.entry = {.count = 1, .reusable = true}}, SHIFT(84),
+ [616] = {.entry = {.count = 1, .reusable = true}}, SHIFT(85),
+ [618] = {.entry = {.count = 1, .reusable = true}}, SHIFT(49),
+ [620] = {.entry = {.count = 1, .reusable = true}}, SHIFT(153),
+ [622] = {.entry = {.count = 1, .reusable = true}}, SHIFT(80),
+ [624] = {.entry = {.count = 1, .reusable = true}}, SHIFT(184),
+ [626] = {.entry = {.count = 1, .reusable = true}}, SHIFT(50),
+ [628] = {.entry = {.count = 1, .reusable = true}}, SHIFT(91),
+ [630] = {.entry = {.count = 1, .reusable = true}}, SHIFT(147),
+ [632] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_prefix, 2, 0, 6),
+ [634] = {.entry = {.count = 1, .reusable = true}}, SHIFT(185),
+ [636] = {.entry = {.count = 1, .reusable = true}}, SHIFT(246),
+ [638] = {.entry = {.count = 1, .reusable = true}}, SHIFT(78),
+ [640] = {.entry = {.count = 1, .reusable = true}}, SHIFT(92),
+ [642] = {.entry = {.count = 1, .reusable = true}}, SHIFT(86),
+ [644] = {.entry = {.count = 1, .reusable = true}}, SHIFT(93),
+ [646] = {.entry = {.count = 1, .reusable = true}}, SHIFT(183),
+ [648] = {.entry = {.count = 1, .reusable = true}}, SHIFT(87),
+ [650] = {.entry = {.count = 1, .reusable = true}}, SHIFT(166),
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#ifdef TREE_SITTER_HIDE_SYMBOLS
+#define TS_PUBLIC
+#elif defined(_WIN32)
+#define TS_PUBLIC __declspec(dllexport)
+#else
+#define TS_PUBLIC __attribute__((visibility("default")))
+#endif
+
+TS_PUBLIC const TSLanguage *tree_sitter_compact(void) {
+ static const TSLanguage language = {
+ .abi_version = LANGUAGE_VERSION,
+ .symbol_count = SYMBOL_COUNT,
+ .alias_count = ALIAS_COUNT,
+ .token_count = TOKEN_COUNT,
+ .external_token_count = EXTERNAL_TOKEN_COUNT,
+ .state_count = STATE_COUNT,
+ .large_state_count = LARGE_STATE_COUNT,
+ .production_id_count = PRODUCTION_ID_COUNT,
+ .supertype_count = SUPERTYPE_COUNT,
+ .field_count = FIELD_COUNT,
+ .max_alias_sequence_length = MAX_ALIAS_SEQUENCE_LENGTH,
+ .parse_table = &ts_parse_table[0][0],
+ .small_parse_table = ts_small_parse_table,
+ .small_parse_table_map = ts_small_parse_table_map,
+ .parse_actions = ts_parse_actions,
+ .symbol_names = ts_symbol_names,
+ .field_names = ts_field_names,
+ .field_map_slices = ts_field_map_slices,
+ .field_map_entries = ts_field_map_entries,
+ .symbol_metadata = ts_symbol_metadata,
+ .public_symbol_map = ts_symbol_map,
+ .alias_map = ts_non_terminal_alias_map,
+ .alias_sequences = &ts_alias_sequences[0][0],
+ .lex_modes = (const void*)ts_lex_modes,
+ .lex_fn = ts_lex,
+ .keyword_lex_fn = ts_lex_keywords,
+ .keyword_capture_token = sym_identifier,
+ .primary_state_ids = ts_primary_state_ids,
+ .name = "compact",
+ .max_reserved_word_set_size = 0,
+ .metadata = {
+ .major_version = 0,
+ .minor_version = 1,
+ .patch_version = 0,
+ },
+ };
+ return &language;
+}
+#ifdef __cplusplus
+}
+#endif
diff --git a/crates/tree-sitter-compact/src/tree_sitter/alloc.h b/crates/tree-sitter-compact/src/tree_sitter/alloc.h
new file mode 100644
index 0000000..1abdd12
--- /dev/null
+++ b/crates/tree-sitter-compact/src/tree_sitter/alloc.h
@@ -0,0 +1,54 @@
+#ifndef TREE_SITTER_ALLOC_H_
+#define TREE_SITTER_ALLOC_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#include
+#include
+
+// Allow clients to override allocation functions
+#ifdef TREE_SITTER_REUSE_ALLOCATOR
+
+extern void *(*ts_current_malloc)(size_t size);
+extern void *(*ts_current_calloc)(size_t count, size_t size);
+extern void *(*ts_current_realloc)(void *ptr, size_t size);
+extern void (*ts_current_free)(void *ptr);
+
+#ifndef ts_malloc
+#define ts_malloc ts_current_malloc
+#endif
+#ifndef ts_calloc
+#define ts_calloc ts_current_calloc
+#endif
+#ifndef ts_realloc
+#define ts_realloc ts_current_realloc
+#endif
+#ifndef ts_free
+#define ts_free ts_current_free
+#endif
+
+#else
+
+#ifndef ts_malloc
+#define ts_malloc malloc
+#endif
+#ifndef ts_calloc
+#define ts_calloc calloc
+#endif
+#ifndef ts_realloc
+#define ts_realloc realloc
+#endif
+#ifndef ts_free
+#define ts_free free
+#endif
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_ALLOC_H_
diff --git a/crates/tree-sitter-compact/src/tree_sitter/array.h b/crates/tree-sitter-compact/src/tree_sitter/array.h
new file mode 100644
index 0000000..a17a574
--- /dev/null
+++ b/crates/tree-sitter-compact/src/tree_sitter/array.h
@@ -0,0 +1,291 @@
+#ifndef TREE_SITTER_ARRAY_H_
+#define TREE_SITTER_ARRAY_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "./alloc.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4101)
+#elif defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-variable"
+#endif
+
+#define Array(T) \
+ struct { \
+ T *contents; \
+ uint32_t size; \
+ uint32_t capacity; \
+ }
+
+/// Initialize an array.
+#define array_init(self) \
+ ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
+
+/// Create an empty array.
+#define array_new() \
+ { NULL, 0, 0 }
+
+/// Get a pointer to the element at a given `index` in the array.
+#define array_get(self, _index) \
+ (assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
+
+/// Get a pointer to the first element in the array.
+#define array_front(self) array_get(self, 0)
+
+/// Get a pointer to the last element in the array.
+#define array_back(self) array_get(self, (self)->size - 1)
+
+/// Clear the array, setting its size to zero. Note that this does not free any
+/// memory allocated for the array's contents.
+#define array_clear(self) ((self)->size = 0)
+
+/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
+/// less than the array's current capacity, this function has no effect.
+#define array_reserve(self, new_capacity) \
+ _array__reserve((Array *)(self), array_elem_size(self), new_capacity)
+
+/// Free any memory allocated for this array. Note that this does not free any
+/// memory allocated for the array's contents.
+#define array_delete(self) _array__delete((Array *)(self))
+
+/// Push a new `element` onto the end of the array.
+#define array_push(self, element) \
+ (_array__grow((Array *)(self), 1, array_elem_size(self)), \
+ (self)->contents[(self)->size++] = (element))
+
+/// Increase the array's size by `count` elements.
+/// New elements are zero-initialized.
+#define array_grow_by(self, count) \
+ do { \
+ if ((count) == 0) break; \
+ _array__grow((Array *)(self), count, array_elem_size(self)); \
+ memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
+ (self)->size += (count); \
+ } while (0)
+
+/// Append all elements from one array to the end of another.
+#define array_push_all(self, other) \
+ array_extend((self), (other)->size, (other)->contents)
+
+/// Append `count` elements to the end of the array, reading their values from the
+/// `contents` pointer.
+#define array_extend(self, count, contents) \
+ _array__splice( \
+ (Array *)(self), array_elem_size(self), (self)->size, \
+ 0, count, contents \
+ )
+
+/// Remove `old_count` elements from the array starting at the given `index`. At
+/// the same index, insert `new_count` new elements, reading their values from the
+/// `new_contents` pointer.
+#define array_splice(self, _index, old_count, new_count, new_contents) \
+ _array__splice( \
+ (Array *)(self), array_elem_size(self), _index, \
+ old_count, new_count, new_contents \
+ )
+
+/// Insert one `element` into the array at the given `index`.
+#define array_insert(self, _index, element) \
+ _array__splice((Array *)(self), array_elem_size(self), _index, 0, 1, &(element))
+
+/// Remove one element from the array at the given `index`.
+#define array_erase(self, _index) \
+ _array__erase((Array *)(self), array_elem_size(self), _index)
+
+/// Pop the last element off the array, returning the element by value.
+#define array_pop(self) ((self)->contents[--(self)->size])
+
+/// Assign the contents of one array to another, reallocating if necessary.
+#define array_assign(self, other) \
+ _array__assign((Array *)(self), (const Array *)(other), array_elem_size(self))
+
+/// Swap one array with another
+#define array_swap(self, other) \
+ _array__swap((Array *)(self), (Array *)(other))
+
+/// Get the size of the array contents
+#define array_elem_size(self) (sizeof *(self)->contents)
+
+/// Search a sorted array for a given `needle` value, using the given `compare`
+/// callback to determine the order.
+///
+/// If an existing element is found to be equal to `needle`, then the `index`
+/// out-parameter is set to the existing value's index, and the `exists`
+/// out-parameter is set to true. Otherwise, `index` is set to an index where
+/// `needle` should be inserted in order to preserve the sorting, and `exists`
+/// is set to false.
+#define array_search_sorted_with(self, compare, needle, _index, _exists) \
+ _array__search_sorted(self, 0, compare, , needle, _index, _exists)
+
+/// Search a sorted array for a given `needle` value, using integer comparisons
+/// of a given struct field (specified with a leading dot) to determine the order.
+///
+/// See also `array_search_sorted_with`.
+#define array_search_sorted_by(self, field, needle, _index, _exists) \
+ _array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
+
+/// Insert a given `value` into a sorted array, using the given `compare`
+/// callback to determine the order.
+#define array_insert_sorted_with(self, compare, value) \
+ do { \
+ unsigned _index, _exists; \
+ array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
+ if (!_exists) array_insert(self, _index, value); \
+ } while (0)
+
+/// Insert a given `value` into a sorted array, using integer comparisons of
+/// a given struct field (specified with a leading dot) to determine the order.
+///
+/// See also `array_search_sorted_by`.
+#define array_insert_sorted_by(self, field, value) \
+ do { \
+ unsigned _index, _exists; \
+ array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
+ if (!_exists) array_insert(self, _index, value); \
+ } while (0)
+
+// Private
+
+typedef Array(void) Array;
+
+/// This is not what you're looking for, see `array_delete`.
+static inline void _array__delete(Array *self) {
+ if (self->contents) {
+ ts_free(self->contents);
+ self->contents = NULL;
+ self->size = 0;
+ self->capacity = 0;
+ }
+}
+
+/// This is not what you're looking for, see `array_erase`.
+static inline void _array__erase(Array *self, size_t element_size,
+ uint32_t index) {
+ assert(index < self->size);
+ char *contents = (char *)self->contents;
+ memmove(contents + index * element_size, contents + (index + 1) * element_size,
+ (self->size - index - 1) * element_size);
+ self->size--;
+}
+
+/// This is not what you're looking for, see `array_reserve`.
+static inline void _array__reserve(Array *self, size_t element_size, uint32_t new_capacity) {
+ if (new_capacity > self->capacity) {
+ if (self->contents) {
+ self->contents = ts_realloc(self->contents, new_capacity * element_size);
+ } else {
+ self->contents = ts_malloc(new_capacity * element_size);
+ }
+ self->capacity = new_capacity;
+ }
+}
+
+/// This is not what you're looking for, see `array_assign`.
+static inline void _array__assign(Array *self, const Array *other, size_t element_size) {
+ _array__reserve(self, element_size, other->size);
+ self->size = other->size;
+ memcpy(self->contents, other->contents, self->size * element_size);
+}
+
+/// This is not what you're looking for, see `array_swap`.
+static inline void _array__swap(Array *self, Array *other) {
+ Array swap = *other;
+ *other = *self;
+ *self = swap;
+}
+
+/// This is not what you're looking for, see `array_push` or `array_grow_by`.
+static inline void _array__grow(Array *self, uint32_t count, size_t element_size) {
+ uint32_t new_size = self->size + count;
+ if (new_size > self->capacity) {
+ uint32_t new_capacity = self->capacity * 2;
+ if (new_capacity < 8) new_capacity = 8;
+ if (new_capacity < new_size) new_capacity = new_size;
+ _array__reserve(self, element_size, new_capacity);
+ }
+}
+
+/// This is not what you're looking for, see `array_splice`.
+static inline void _array__splice(Array *self, size_t element_size,
+ uint32_t index, uint32_t old_count,
+ uint32_t new_count, const void *elements) {
+ uint32_t new_size = self->size + new_count - old_count;
+ uint32_t old_end = index + old_count;
+ uint32_t new_end = index + new_count;
+ assert(old_end <= self->size);
+
+ _array__reserve(self, element_size, new_size);
+
+ char *contents = (char *)self->contents;
+ if (self->size > old_end) {
+ memmove(
+ contents + new_end * element_size,
+ contents + old_end * element_size,
+ (self->size - old_end) * element_size
+ );
+ }
+ if (new_count > 0) {
+ if (elements) {
+ memcpy(
+ (contents + index * element_size),
+ elements,
+ new_count * element_size
+ );
+ } else {
+ memset(
+ (contents + index * element_size),
+ 0,
+ new_count * element_size
+ );
+ }
+ }
+ self->size += new_count - old_count;
+}
+
+/// A binary search routine, based on Rust's `std::slice::binary_search_by`.
+/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
+#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
+ do { \
+ *(_index) = start; \
+ *(_exists) = false; \
+ uint32_t size = (self)->size - *(_index); \
+ if (size == 0) break; \
+ int comparison; \
+ while (size > 1) { \
+ uint32_t half_size = size / 2; \
+ uint32_t mid_index = *(_index) + half_size; \
+ comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
+ if (comparison <= 0) *(_index) = mid_index; \
+ size -= half_size; \
+ } \
+ comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
+ if (comparison == 0) *(_exists) = true; \
+ else if (comparison < 0) *(_index) += 1; \
+ } while (0)
+
+/// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
+/// parameter by reference in order to work with the generic sorting function above.
+#define _compare_int(a, b) ((int)*(a) - (int)(b))
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#elif defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_ARRAY_H_
diff --git a/crates/tree-sitter-compact/src/tree_sitter/parser.h b/crates/tree-sitter-compact/src/tree_sitter/parser.h
new file mode 100644
index 0000000..858107d
--- /dev/null
+++ b/crates/tree-sitter-compact/src/tree_sitter/parser.h
@@ -0,0 +1,286 @@
+#ifndef TREE_SITTER_PARSER_H_
+#define TREE_SITTER_PARSER_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#include
+#include
+
+#define ts_builtin_sym_error ((TSSymbol)-1)
+#define ts_builtin_sym_end 0
+#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
+
+#ifndef TREE_SITTER_API_H_
+typedef uint16_t TSStateId;
+typedef uint16_t TSSymbol;
+typedef uint16_t TSFieldId;
+typedef struct TSLanguage TSLanguage;
+typedef struct TSLanguageMetadata {
+ uint8_t major_version;
+ uint8_t minor_version;
+ uint8_t patch_version;
+} TSLanguageMetadata;
+#endif
+
+typedef struct {
+ TSFieldId field_id;
+ uint8_t child_index;
+ bool inherited;
+} TSFieldMapEntry;
+
+// Used to index the field and supertype maps.
+typedef struct {
+ uint16_t index;
+ uint16_t length;
+} TSMapSlice;
+
+typedef struct {
+ bool visible;
+ bool named;
+ bool supertype;
+} TSSymbolMetadata;
+
+typedef struct TSLexer TSLexer;
+
+struct TSLexer {
+ int32_t lookahead;
+ TSSymbol result_symbol;
+ void (*advance)(TSLexer *, bool);
+ void (*mark_end)(TSLexer *);
+ uint32_t (*get_column)(TSLexer *);
+ bool (*is_at_included_range_start)(const TSLexer *);
+ bool (*eof)(const TSLexer *);
+ void (*log)(const TSLexer *, const char *, ...);
+};
+
+typedef enum {
+ TSParseActionTypeShift,
+ TSParseActionTypeReduce,
+ TSParseActionTypeAccept,
+ TSParseActionTypeRecover,
+} TSParseActionType;
+
+typedef union {
+ struct {
+ uint8_t type;
+ TSStateId state;
+ bool extra;
+ bool repetition;
+ } shift;
+ struct {
+ uint8_t type;
+ uint8_t child_count;
+ TSSymbol symbol;
+ int16_t dynamic_precedence;
+ uint16_t production_id;
+ } reduce;
+ uint8_t type;
+} TSParseAction;
+
+typedef struct {
+ uint16_t lex_state;
+ uint16_t external_lex_state;
+} TSLexMode;
+
+typedef struct {
+ uint16_t lex_state;
+ uint16_t external_lex_state;
+ uint16_t reserved_word_set_id;
+} TSLexerMode;
+
+typedef union {
+ TSParseAction action;
+ struct {
+ uint8_t count;
+ bool reusable;
+ } entry;
+} TSParseActionEntry;
+
+typedef struct {
+ int32_t start;
+ int32_t end;
+} TSCharacterRange;
+
+struct TSLanguage {
+ uint32_t abi_version;
+ uint32_t symbol_count;
+ uint32_t alias_count;
+ uint32_t token_count;
+ uint32_t external_token_count;
+ uint32_t state_count;
+ uint32_t large_state_count;
+ uint32_t production_id_count;
+ uint32_t field_count;
+ uint16_t max_alias_sequence_length;
+ const uint16_t *parse_table;
+ const uint16_t *small_parse_table;
+ const uint32_t *small_parse_table_map;
+ const TSParseActionEntry *parse_actions;
+ const char * const *symbol_names;
+ const char * const *field_names;
+ const TSMapSlice *field_map_slices;
+ const TSFieldMapEntry *field_map_entries;
+ const TSSymbolMetadata *symbol_metadata;
+ const TSSymbol *public_symbol_map;
+ const uint16_t *alias_map;
+ const TSSymbol *alias_sequences;
+ const TSLexerMode *lex_modes;
+ bool (*lex_fn)(TSLexer *, TSStateId);
+ bool (*keyword_lex_fn)(TSLexer *, TSStateId);
+ TSSymbol keyword_capture_token;
+ struct {
+ const bool *states;
+ const TSSymbol *symbol_map;
+ void *(*create)(void);
+ void (*destroy)(void *);
+ bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
+ unsigned (*serialize)(void *, char *);
+ void (*deserialize)(void *, const char *, unsigned);
+ } external_scanner;
+ const TSStateId *primary_state_ids;
+ const char *name;
+ const TSSymbol *reserved_words;
+ uint16_t max_reserved_word_set_size;
+ uint32_t supertype_count;
+ const TSSymbol *supertype_symbols;
+ const TSMapSlice *supertype_map_slices;
+ const TSSymbol *supertype_map_entries;
+ TSLanguageMetadata metadata;
+};
+
+static inline bool set_contains(const TSCharacterRange *ranges, uint32_t len, int32_t lookahead) {
+ uint32_t index = 0;
+ uint32_t size = len - index;
+ while (size > 1) {
+ uint32_t half_size = size / 2;
+ uint32_t mid_index = index + half_size;
+ const TSCharacterRange *range = &ranges[mid_index];
+ if (lookahead >= range->start && lookahead <= range->end) {
+ return true;
+ } else if (lookahead > range->end) {
+ index = mid_index;
+ }
+ size -= half_size;
+ }
+ const TSCharacterRange *range = &ranges[index];
+ return (lookahead >= range->start && lookahead <= range->end);
+}
+
+/*
+ * Lexer Macros
+ */
+
+#ifdef _MSC_VER
+#define UNUSED __pragma(warning(suppress : 4101))
+#else
+#define UNUSED __attribute__((unused))
+#endif
+
+#define START_LEXER() \
+ bool result = false; \
+ bool skip = false; \
+ UNUSED \
+ bool eof = false; \
+ int32_t lookahead; \
+ goto start; \
+ next_state: \
+ lexer->advance(lexer, skip); \
+ start: \
+ skip = false; \
+ lookahead = lexer->lookahead;
+
+#define ADVANCE(state_value) \
+ { \
+ state = state_value; \
+ goto next_state; \
+ }
+
+#define ADVANCE_MAP(...) \
+ { \
+ static const uint16_t map[] = { __VA_ARGS__ }; \
+ for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \
+ if (map[i] == lookahead) { \
+ state = map[i + 1]; \
+ goto next_state; \
+ } \
+ } \
+ }
+
+#define SKIP(state_value) \
+ { \
+ skip = true; \
+ state = state_value; \
+ goto next_state; \
+ }
+
+#define ACCEPT_TOKEN(symbol_value) \
+ result = true; \
+ lexer->result_symbol = symbol_value; \
+ lexer->mark_end(lexer);
+
+#define END_STATE() return result;
+
+/*
+ * Parse Table Macros
+ */
+
+#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
+
+#define STATE(id) id
+
+#define ACTIONS(id) id
+
+#define SHIFT(state_value) \
+ {{ \
+ .shift = { \
+ .type = TSParseActionTypeShift, \
+ .state = (state_value) \
+ } \
+ }}
+
+#define SHIFT_REPEAT(state_value) \
+ {{ \
+ .shift = { \
+ .type = TSParseActionTypeShift, \
+ .state = (state_value), \
+ .repetition = true \
+ } \
+ }}
+
+#define SHIFT_EXTRA() \
+ {{ \
+ .shift = { \
+ .type = TSParseActionTypeShift, \
+ .extra = true \
+ } \
+ }}
+
+#define REDUCE(symbol_name, children, precedence, prod_id) \
+ {{ \
+ .reduce = { \
+ .type = TSParseActionTypeReduce, \
+ .symbol = symbol_name, \
+ .child_count = children, \
+ .dynamic_precedence = precedence, \
+ .production_id = prod_id \
+ }, \
+ }}
+
+#define RECOVER() \
+ {{ \
+ .type = TSParseActionTypeRecover \
+ }}
+
+#define ACCEPT_INPUT() \
+ {{ \
+ .type = TSParseActionTypeAccept \
+ }}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_PARSER_H_
diff --git a/crates/tree-sitter-compact/test/corpus/circuits-and-constructors.txt b/crates/tree-sitter-compact/test/corpus/circuits-and-constructors.txt
new file mode 100644
index 0000000..0290bca
--- /dev/null
+++ b/crates/tree-sitter-compact/test/corpus/circuits-and-constructors.txt
@@ -0,0 +1,152 @@
+================================================================================
+plain circuit
+================================================================================
+
+circuit assertInitialized(): [] {
+ assert(_isInitialized, "not initialized");
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (circuit_declaration
+ name: (identifier)
+ parameters: (parameter_list)
+ type: (type_expression)
+ body: (block
+ (string))))
+
+================================================================================
+exported circuit
+================================================================================
+
+export circuit owner(): Either, ContractAddress> {
+ return _owner;
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (circuit_declaration
+ name: (identifier)
+ parameters: (parameter_list)
+ type: (type_expression
+ (identifier)
+ (generic_arguments
+ (identifier)
+ (generic_arguments
+ (number))
+ (identifier)))
+ body: (block)))
+
+================================================================================
+exported pure circuit
+================================================================================
+
+export pure circuit add(a: Uint<32>, b: Uint<32>): Uint<64> {
+ return a + b;
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (circuit_declaration
+ name: (identifier)
+ parameters: (parameter_list)
+ type: (type_expression
+ (identifier)
+ (generic_arguments
+ (number)))
+ body: (block)))
+
+================================================================================
+pure circuit
+================================================================================
+
+pure circuit half(x: Field): Field {
+ return x;
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (circuit_declaration
+ name: (identifier)
+ parameters: (parameter_list)
+ type: (type_expression
+ (identifier))
+ body: (block)))
+
+================================================================================
+generic circuit
+================================================================================
+
+export circuit pick<#T, U>(a: T, b: U): T {
+ return a;
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (circuit_declaration
+ name: (identifier)
+ (generic_parameters
+ (generic_parameter
+ name: (identifier))
+ (generic_parameter
+ name: (identifier)))
+ parameters: (parameter_list)
+ type: (type_expression
+ (identifier))
+ body: (block)))
+
+================================================================================
+circuit with a destructuring parameter
+================================================================================
+
+circuit unpack({ left, right }: Pair): [] {
+ return [];
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (circuit_declaration
+ name: (identifier)
+ parameters: (parameter_list
+ (block))
+ type: (type_expression)
+ body: (block)))
+
+================================================================================
+circuit body with braces inside a string
+================================================================================
+
+circuit shout(): [] {
+ assert(false, "} not a brace {");
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (circuit_declaration
+ name: (identifier)
+ parameters: (parameter_list)
+ type: (type_expression)
+ body: (block
+ (string))))
+
+================================================================================
+constructor
+================================================================================
+
+constructor(initialOwner: Either, ContractAddress>) {
+ Ownable_initialize(initialOwner);
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (constructor_declaration
+ (parameter_list)
+ (block)))
diff --git a/crates/tree-sitter-compact/test/corpus/comments.txt b/crates/tree-sitter-compact/test/corpus/comments.txt
new file mode 100644
index 0000000..edeb558
--- /dev/null
+++ b/crates/tree-sitter-compact/test/corpus/comments.txt
@@ -0,0 +1,134 @@
+================================================================================
+line comment
+================================================================================
+
+// a note
+ledger _x: Field;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (line_comment)
+ (ledger_declaration
+ name: (identifier)
+ type: (type_expression
+ (identifier))))
+
+================================================================================
+block comment
+================================================================================
+
+/* a note */
+ledger _x: Field;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (block_comment)
+ (ledger_declaration
+ name: (identifier)
+ type: (type_expression
+ (identifier))))
+
+================================================================================
+empty block comment
+================================================================================
+
+/**/
+ledger _x: Field;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (block_comment)
+ (ledger_declaration
+ name: (identifier)
+ type: (type_expression
+ (identifier))))
+
+================================================================================
+doc comment
+================================================================================
+
+/** @description A flag. */
+ledger _x: Boolean;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (doc_comment)
+ (ledger_declaration
+ name: (identifier)
+ type: (type_expression
+ (identifier))))
+
+================================================================================
+doc comment before an exported circuit
+================================================================================
+
+/**
+ * @description Returns the owner.
+ * @circuitInfo k=7, rows=76
+ */
+export circuit owner(): Bytes<32> {
+ return _owner;
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (doc_comment)
+ (circuit_declaration
+ name: (identifier)
+ parameters: (parameter_list)
+ type: (type_expression
+ (identifier)
+ (generic_arguments
+ (number)))
+ body: (block)))
+
+================================================================================
+doc comment before an exported module
+================================================================================
+
+/** @module Ownable */
+export module Ownable {
+ /** @description A flag. */
+ export ledger _isInitialized: Boolean;
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (doc_comment)
+ (module_declaration
+ name: (identifier)
+ body: (module_body
+ (doc_comment)
+ (ledger_declaration
+ name: (identifier)
+ type: (type_expression
+ (identifier))))))
+
+================================================================================
+comments inside a circuit body
+================================================================================
+
+circuit noop(): [] {
+ // a line
+ /* a block */
+ /** a doc */
+ return [];
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (circuit_declaration
+ name: (identifier)
+ parameters: (parameter_list)
+ type: (type_expression)
+ body: (block
+ (line_comment)
+ (block_comment)
+ (doc_comment))))
diff --git a/crates/tree-sitter-compact/test/corpus/imports.txt b/crates/tree-sitter-compact/test/corpus/imports.txt
new file mode 100644
index 0000000..b8bef8a
--- /dev/null
+++ b/crates/tree-sitter-compact/test/corpus/imports.txt
@@ -0,0 +1,141 @@
+================================================================================
+bare module import
+================================================================================
+
+import CompactStandardLibrary;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (import_declaration
+ name: (identifier)))
+
+================================================================================
+path import
+================================================================================
+
+import "./Foo";
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (import_declaration
+ name: (string)))
+
+================================================================================
+module import with prefix
+================================================================================
+
+import Foo prefix Foo_;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (import_declaration
+ name: (identifier)
+ (import_prefix
+ prefix: (identifier))))
+
+================================================================================
+path import with prefix
+================================================================================
+
+import "./Foo" prefix F_;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (import_declaration
+ name: (string)
+ (import_prefix
+ prefix: (identifier))))
+
+================================================================================
+import with generic arguments
+================================================================================
+
+import Foo prefix X_;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (import_declaration
+ name: (identifier)
+ (generic_arguments
+ (identifier)
+ (identifier))
+ (import_prefix
+ prefix: (identifier))))
+
+================================================================================
+path import with nested generic arguments
+================================================================================
+
+import "../../Signer"> prefix Signer_;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (import_declaration
+ name: (string)
+ (generic_arguments
+ (identifier)
+ (generic_arguments
+ (identifier)
+ (identifier)))
+ (import_prefix
+ prefix: (identifier))))
+
+================================================================================
+import selection
+================================================================================
+
+import { _name, _symbol } from "./Core";
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (import_declaration
+ (import_selection
+ (import_element
+ (identifier))
+ (import_element
+ (identifier)))
+ (string)))
+
+================================================================================
+import selection with alias and prefix
+================================================================================
+
+import { selfAsRecipient, UINT128_MAX as MAX } from "../utils/Utils" prefix Utils_;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (import_declaration
+ (import_selection
+ (import_element
+ (identifier))
+ (import_element
+ (identifier)
+ (identifier)))
+ (string)
+ (import_prefix
+ (identifier))))
+
+================================================================================
+import selection from a bare module
+================================================================================
+
+import { ZswapCoinPublicKey, Either } from CompactStandardLibrary;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (import_declaration
+ (import_selection
+ (import_element
+ (identifier))
+ (import_element
+ (identifier)))
+ (identifier)))
diff --git a/crates/tree-sitter-compact/test/corpus/modules-and-exports.txt b/crates/tree-sitter-compact/test/corpus/modules-and-exports.txt
new file mode 100644
index 0000000..29eebea
--- /dev/null
+++ b/crates/tree-sitter-compact/test/corpus/modules-and-exports.txt
@@ -0,0 +1,116 @@
+================================================================================
+module with declarations in its body
+================================================================================
+
+module Ownable {
+ import CompactStandardLibrary;
+ export ledger _owner: Bytes<32>;
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (module_declaration
+ name: (identifier)
+ body: (module_body
+ (import_declaration
+ name: (identifier))
+ (ledger_declaration
+ name: (identifier)
+ type: (type_expression
+ (identifier)
+ (generic_arguments
+ (number)))))))
+
+================================================================================
+exported generic module
+================================================================================
+
+export module Signer {
+ export circuit check(x: T): Boolean { return true; }
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (module_declaration
+ name: (identifier)
+ (generic_parameters
+ (generic_parameter
+ name: (identifier)))
+ body: (module_body
+ (circuit_declaration
+ name: (identifier)
+ parameters: (parameter_list)
+ type: (type_expression
+ (identifier))
+ body: (block)))))
+
+================================================================================
+nested modules
+================================================================================
+
+module Outer {
+ module Inner {
+ export ledger _flag: Boolean;
+ }
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (module_declaration
+ name: (identifier)
+ body: (module_body
+ (module_declaration
+ name: (identifier)
+ body: (module_body
+ (ledger_declaration
+ name: (identifier)
+ type: (type_expression
+ (identifier))))))))
+
+================================================================================
+export list
+================================================================================
+
+export { A, B };
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (export_declaration
+ (identifier)
+ (identifier)))
+
+================================================================================
+export list without a trailing semicolon
+================================================================================
+
+export { A, B, }
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (export_declaration
+ (identifier)
+ (identifier)))
+
+================================================================================
+export list inside a module
+================================================================================
+
+module M {
+ export { _name, _symbol, _decimals };
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (module_declaration
+ name: (identifier)
+ body: (module_body
+ (export_declaration
+ (identifier)
+ (identifier)
+ (identifier)))))
diff --git a/crates/tree-sitter-compact/test/corpus/pragma-and-include.txt b/crates/tree-sitter-compact/test/corpus/pragma-and-include.txt
new file mode 100644
index 0000000..47ec355
--- /dev/null
+++ b/crates/tree-sitter-compact/test/corpus/pragma-and-include.txt
@@ -0,0 +1,37 @@
+================================================================================
+pragma with a lower bound
+================================================================================
+
+pragma language_version >= 0.23.0;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (pragma_declaration
+ name: (identifier)
+ (version_constraint)))
+
+================================================================================
+pragma with alternative versions
+================================================================================
+
+pragma language_version 0.16 || 0.17;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (pragma_declaration
+ name: (identifier)
+ (version_constraint)))
+
+================================================================================
+include
+================================================================================
+
+include "./shared";
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (include_declaration
+ path: (string)))
diff --git a/crates/tree-sitter-compact/test/corpus/structs-enums-contracts.txt b/crates/tree-sitter-compact/test/corpus/structs-enums-contracts.txt
new file mode 100644
index 0000000..50a7918
--- /dev/null
+++ b/crates/tree-sitter-compact/test/corpus/structs-enums-contracts.txt
@@ -0,0 +1,105 @@
+================================================================================
+struct
+================================================================================
+
+struct Val {
+ a: Field;
+ b: Boolean;
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (struct_declaration
+ name: (identifier)
+ body: (block)))
+
+================================================================================
+exported struct with a trailing semicolon
+================================================================================
+
+export struct Ciphertext {
+ c1: CurvePoint,
+ c2: CurvePoint,
+};
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (struct_declaration
+ name: (identifier)
+ body: (block)))
+
+================================================================================
+generic struct
+================================================================================
+
+export struct Box {
+ value: T;
+}
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (struct_declaration
+ name: (identifier)
+ (generic_parameters
+ (generic_parameter
+ name: (identifier)))
+ body: (block)))
+
+================================================================================
+enum
+================================================================================
+
+enum Color { red, white, blue }
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (enum_declaration
+ name: (identifier)
+ body: (block)))
+
+================================================================================
+exported enum with a trailing semicolon
+================================================================================
+
+export enum ProposalStatus { Pending, Approved, Rejected, };
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (enum_declaration
+ name: (identifier)
+ body: (block)))
+
+================================================================================
+external contract declaration
+================================================================================
+
+export contract Token {
+ circuit balanceOf(owner: Bytes<32>): Uint<128>;
+ pure circuit name(): Opaque<"string">;
+};
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (contract_declaration
+ name: (identifier)
+ body: (block
+ (string))))
+
+================================================================================
+contract implements
+================================================================================
+
+contract implements Token;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (implements_declaration
+ (type_expression
+ (identifier))))
diff --git a/crates/tree-sitter-compact/test/corpus/types-ledger-witness.txt b/crates/tree-sitter-compact/test/corpus/types-ledger-witness.txt
new file mode 100644
index 0000000..67c6b33
--- /dev/null
+++ b/crates/tree-sitter-compact/test/corpus/types-ledger-witness.txt
@@ -0,0 +1,149 @@
+================================================================================
+ledger field
+================================================================================
+
+ledger _counter: Counter;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (ledger_declaration
+ name: (identifier)
+ type: (type_expression
+ (identifier))))
+
+================================================================================
+exported ledger field
+================================================================================
+
+export ledger _owner: Either, ContractAddress>;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (ledger_declaration
+ name: (identifier)
+ type: (type_expression
+ (identifier)
+ (generic_arguments
+ (identifier)
+ (generic_arguments
+ (number))
+ (identifier)))))
+
+================================================================================
+exported sealed ledger field
+================================================================================
+
+export sealed ledger _domain: Bytes<32>;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (ledger_declaration
+ name: (identifier)
+ type: (type_expression
+ (identifier)
+ (generic_arguments
+ (number)))))
+
+================================================================================
+ledger field with a tuple type
+================================================================================
+
+ledger _pair: [Field, Boolean];
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (ledger_declaration
+ name: (identifier)
+ type: (type_expression
+ (identifier)
+ (identifier))))
+
+================================================================================
+witness
+================================================================================
+
+witness wit_CallerSK(): Bytes<32>;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (witness_declaration
+ name: (identifier)
+ parameters: (parameter_list)
+ type: (type_expression
+ (identifier)
+ (generic_arguments
+ (number)))))
+
+================================================================================
+exported witness with parameters
+================================================================================
+
+export witness wit_balance(ct: ElGamal_Ciphertext): Uint<128>;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (witness_declaration
+ name: (identifier)
+ parameters: (parameter_list)
+ type: (type_expression
+ (identifier)
+ (generic_arguments
+ (number)))))
+
+================================================================================
+type alias
+================================================================================
+
+type Hash = Bytes<32>;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (type_declaration
+ name: (identifier)
+ value: (type_expression
+ (identifier)
+ (generic_arguments
+ (number)))))
+
+================================================================================
+exported new type alias
+================================================================================
+
+export new type AccountIdentifier = Bytes<32>;
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (type_declaration
+ name: (identifier)
+ value: (type_expression
+ (identifier)
+ (generic_arguments
+ (number)))))
+
+================================================================================
+generic type alias
+================================================================================
+
+export type Pair = [A, B];
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (type_declaration
+ name: (identifier)
+ (generic_parameters
+ (generic_parameter
+ name: (identifier))
+ (generic_parameter
+ name: (identifier)))
+ value: (type_expression
+ (identifier)
+ (identifier))))
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/NOTICE b/crates/tree-sitter-compact/test/fixtures/upstream/NOTICE
new file mode 100644
index 0000000..9352fb8
--- /dev/null
+++ b/crates/tree-sitter-compact/test/fixtures/upstream/NOTICE
@@ -0,0 +1,17 @@
+The `.compact` files in this directory are copied verbatim from the Compact
+compiler repository and are used here only as parser fixtures.
+
+Source: https://github.com/LFDT-Minokawa/compact
+Path: examples/formatter/output/
+Commit: c47230cc8c3e743634166d9b84698ac2b238c418
+License: Apache-2.0
+Copyright: Copyright (C) 2025 Midnight Foundation
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+these files except in compliance with the License. You may obtain a copy of the
+License at http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software distributed
+under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied. See the License for the
+specific language governing permissions and limitations under the License.
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/already_formatted.compact b/crates/tree-sitter-compact/test/fixtures/upstream/already_formatted.compact
new file mode 100644
index 0000000..800fb64
--- /dev/null
+++ b/crates/tree-sitter-compact/test/fixtures/upstream/already_formatted.compact
@@ -0,0 +1,26 @@
+// This file is part of Compact.
+// Copyright (C) 2025 Midnight Foundation
+// SPDX-License-Identifier: Apache-2.0
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import CompactStandardLibrary;
+
+export ledger round: Counter;
+
+export circuit increment(): [] {
+ round.increment(1);
+}
+
+export circuit decrement(): [] {
+ round.decrement(1);
+}
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_1.compact b/crates/tree-sitter-compact/test/fixtures/upstream/example_1.compact
new file mode 100644
index 0000000..bae9c5b
--- /dev/null
+++ b/crates/tree-sitter-compact/test/fixtures/upstream/example_1.compact
@@ -0,0 +1,34 @@
+// This file is part of Compact.
+// Copyright (C) 2025 Midnight Foundation
+// SPDX-License-Identifier: Apache-2.0
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import CompactStandardLibrary;
+
+export circuit method(): [] {
+ const p = 1;
+ const z = 2;
+ const x = 3;
+}
+
+constructor() {
+ const p: Field = 1;
+
+ const z: Boolean = true;
+
+ const y = default;
+
+ const f = default> as Field;
+}
+
+witness method_2(): Boolean;
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_10.compact b/crates/tree-sitter-compact/test/fixtures/upstream/example_10.compact
new file mode 100644
index 0000000..e6274da
--- /dev/null
+++ b/crates/tree-sitter-compact/test/fixtures/upstream/example_10.compact
@@ -0,0 +1,62 @@
+// This file is part of Compact.
+// Copyright (C) 2025 Midnight Foundation
+// SPDX-License-Identifier: Apache-2.0
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import CompactStandardLibrary;
+
+export ledger c: Counter;
+
+export circuit decrement(amount: Uint<16>): [] {
+ return c.decrement(amount);
+}
+
+export circuit increment(amount: Uint<16>): [] {
+ for (const f of 1..10) {
+ c.increment(1);
+ }
+ return c.increment(amount);
+}
+
+export circuit reset_to_default(): [] {
+ assert(1 < 2, "hoho");
+ return c.reset_to_default();
+}
+
+export circuit read(): Uint<64> {
+ if (1 == 2)
+
+ {
+ return c.read();
+ }
+ return c.read();
+}
+
+struct Val {
+ x: Field,
+ y: Boolean,
+ f: Field,
+
+ x: Opaque<"string">,
+
+ d: Vector<1, Boolean>,
+}
+
+enum french {
+ a,
+ bm,
+ c,
+ d,
+}
+
+export { Maybe };
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_11.compact b/crates/tree-sitter-compact/test/fixtures/upstream/example_11.compact
new file mode 100644
index 0000000..062f535
--- /dev/null
+++ b/crates/tree-sitter-compact/test/fixtures/upstream/example_11.compact
@@ -0,0 +1,32 @@
+// This file is part of Compact.
+// Copyright (C) 2025 Midnight Foundation
+// SPDX-License-Identifier: Apache-2.0
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+struct mariusz {
+ x: Field,
+ y: Field,
+ z: Field,
+}
+
+export circuit bob(a: Field, x: Field,): [Uint<32>, Uint<32>,] {
+ return [1, 2,];
+}
+
+constructor() {
+
+ const p = [1, 1, 1, 1, 1, 1, 1, 1, 1,];
+ const f = mariusz { 1, 2, 3, };
+ const g = [bob(1, 2,), bob(1, 2,), bob(1, 2,),];
+ const phil = [1, 2, 3, [1, 2, 3,],];
+}
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_12.compact b/crates/tree-sitter-compact/test/fixtures/upstream/example_12.compact
new file mode 100644
index 0000000..7d1f0d3
--- /dev/null
+++ b/crates/tree-sitter-compact/test/fixtures/upstream/example_12.compact
@@ -0,0 +1,36 @@
+// This file is part of Compact.
+// Copyright (C) 2025 Midnight Foundation
+// SPDX-License-Identifier: Apache-2.0
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+struct var9a {
+ a: Field,
+ b: Field
+}
+
+struct var9b {
+ a: var9a,
+ b: var9a
+}
+
+struct var9c {
+ a: var9b,
+ b: var9b
+}
+
+export circuit test9(): [] {
+ const p = var9c { a: var9b { a: var9a { 1, 2 }, b: var9a { 1, 2 } },
+ b: var9b { a: var9a { 1, 2 }, b: var9a { 1, 2 }, } },
+ x = 1,
+ y = p.a.a.a + p.b.b.b;
+}
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_13.compact b/crates/tree-sitter-compact/test/fixtures/upstream/example_13.compact
new file mode 100644
index 0000000..34734d9
--- /dev/null
+++ b/crates/tree-sitter-compact/test/fixtures/upstream/example_13.compact
@@ -0,0 +1,185 @@
+// This file is part of Compact.
+// Copyright (C) 2025 Midnight Foundation
+// SPDX-License-Identifier: Apache-2.0
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import CompactStandardLibrary;
+
+export ledger total_operations: Counter;
+
+export ledger last_calculated_result: Field;
+
+export ledger operation_category_code: Uint<8>;
+
+export ledger result_value: Field;
+
+export ledger multiplier: Field;
+
+export ledger op_multiplier: Field;
+
+witness get_user_calculation_parameters_from_external_validation_source(
+ user_identifier_hash: Field,
+ initial_amount_value: Uint<64>,
+ scaling_factor_multiplier: Uint<64>,
+ offset_adjustment_value: Uint<64>
+ ): Uint<64>;
+
+witness retrieve_transaction_validation_context_and_authorization_flags(
+ transaction_reference_id: Field,
+ sender_account_identifier: Field,
+ receiver_account_identifier: Field,
+ operation_type_code: Uint<8>
+ ): [Boolean, Uint<64>];
+
+circuit perform_complex_arithmetic_calculation_with_multiple_inputs(
+ base_calculation_value: Uint<64>,
+ primary_scaling_factor: Uint<64>,
+ secondary_offset_amount: Uint<64>,
+ final_adjustment_modifier: Uint<64>
+ ): Uint<64> {
+ return ((base_calculation_value * primary_scaling_factor) + secondary_offset_amount) +
+ final_adjustment_modifier;
+}
+
+export circuit execute_tiered_bonus_calculation_based_on_user_metrics(
+ user_account_balance: Uint<64>,
+ transaction_total_amount: Uint<64>,
+ accumulated_loyalty_points: Uint<64>,
+ account_age_in_months: Uint<64>
+ ): Uint<64> {
+ const base = transaction_total_amount * 10 as Uint<64>;
+ const loyalty = accumulated_loyalty_points * 2 as Uint<64>;
+ const age = account_age_in_months * 3 as Uint<64>;
+ return (base + loyalty) + age;
+}
+
+export circuit test_multiline_call_expressions(input_value: Uint<64>): Uint<64> {
+ const result_from_arithmetic =
+ perform_complex_arithmetic_calculation_with_multiple_inputs(
+ input_value,
+ 10 as Uint<64>,
+ 5 as Uint<64>,
+ 2 as Uint<64>
+ );
+ const bonus_calculation_result =
+ execute_tiered_bonus_calculation_based_on_user_metrics(
+ input_value * 2 as Uint<64>,
+ input_value + 100 as Uint<64>,
+ 50 as Uint<64>,
+ 12 as Uint<64>
+ );
+ return result_from_arithmetic + bonus_calculation_result;
+}
+
+export circuit test_anonymous_circuit_in_fold(values: Vector<4, Uint<64>>): Uint<64> {
+ const sum_result =
+ fold((accumulator_running_total: Uint<64>, current_element_value: Uint<64>): Uint<64> => {
+ const intermediate_calculation = accumulator_running_total + current_element_value;
+ return intermediate_calculation;
+ },
+ 0 as Uint<64>,
+ values);
+ return sum_result;
+}
+
+export circuit test_complex_anonymous_circuit(input_vector: Vector<4, Uint<64>>): Uint<64> {
+ const transformed =
+ fold((running_accumulator_total: Uint<64>, vector_element_value: Uint<64>): Uint<64> => {
+ const doubled_value = vector_element_value * 2 as Uint<64>;
+ const with_bonus_added = doubled_value + 10 as Uint<64>;
+ return running_accumulator_total + with_bonus_added;
+ },
+ 0 as Uint<64>,
+ input_vector);
+ return transformed;
+}
+
+export circuit test_map_anonymous(input: Vector<4, Uint<64>>): Vector<4, Uint<64>> {
+ const result = map((element_value: Uint<64>): Uint<64> => {
+ const step_one = element_value * 3 as Uint<64>;
+ const step_two = step_one + 7 as Uint<64>;
+ return step_two;
+ },
+ input);
+ return result;
+}
+
+export circuit test_if_else_chain(operation_code: Uint<8>): Uint<64> {
+ result_value = 0 as Field;
+ if (operation_code == (0 as Uint<8>)) {
+ result_value = 100 as Field;
+ } else if (operation_code == (1 as Uint<8>)) {
+ result_value = 200 as Field;
+ } else if (operation_code == (2 as Uint<8>)) {
+ result_value = 300 as Field;
+ } else if (operation_code == (3 as Uint<8>)) {
+ result_value = 400 as Field;
+ } else if (operation_code == (4 as Uint<8>)) {
+ result_value = 500 as Field;
+ } else {
+ result_value = 0 as Field;
+ }
+ return result_value as Uint<64>;
+}
+
+export circuit test_complex_if_else(category: Uint<8>, amount: Uint<64>): Uint<64> {
+ multiplier = 100 as Field;
+ if (category == (0 as Uint<8>)) {
+ multiplier = 100 as Field;
+ } else if (category == (1 as Uint<8>)) {
+ multiplier = 110 as Field;
+ } else if (category == (2 as Uint<8>)) {
+ multiplier = 125 as Field;
+ } else if (category == (3 as Uint<8>)) {
+ multiplier = 150 as Field;
+ } else if (category == (4 as Uint<8>)) {
+ multiplier = 200 as Field;
+ } else {
+ multiplier = 100 as Field;
+ }
+ return (amount * (multiplier as Uint<64>));
+}
+
+export circuit test_all_scenarios_combined(
+ op_type: Uint<8>,
+ base_amount: Uint<64>,
+ test_values: Vector<4, Uint<64>>
+ ): Uint<64> {
+ total_operations.increment(1 as Uint<16>);
+ const arithmetic_result =
+ perform_complex_arithmetic_calculation_with_multiple_inputs(
+ base_amount,
+ 2 as Uint<64>,
+ 10 as Uint<64>,
+ 5 as Uint<64>
+ );
+ const vector_sum =
+ fold((acc: Uint<64>, val: Uint<64>): Uint<64> => {
+ return acc + val;
+ },
+ 0 as Uint<64>,
+ test_values);
+ op_multiplier = 1 as Field;
+ if (op_type == (0 as Uint<8>)) {
+ op_multiplier = 1 as Field;
+ } else if (op_type == (1 as Uint<8>)) {
+ op_multiplier = 2 as Field;
+ } else if (op_type == (2 as Uint<8>)) {
+ op_multiplier = 3 as Field;
+ } else {
+ op_multiplier = 1 as Field;
+ }
+ last_calculated_result = ((arithmetic_result + vector_sum) * (op_multiplier as Uint<64>)) as Field;
+ operation_category_code = op_type;
+ return last_calculated_result as Uint<64>;
+}
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_2.compact b/crates/tree-sitter-compact/test/fixtures/upstream/example_2.compact
new file mode 100644
index 0000000..364c3f2
--- /dev/null
+++ b/crates/tree-sitter-compact/test/fixtures/upstream/example_2.compact
@@ -0,0 +1,40 @@
+// This file is part of Compact.
+// Copyright (C) 2025 Midnight Foundation
+// SPDX-License-Identifier: Apache-2.0
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import CompactStandardLibrary;
+
+export circuit method_3(): [] {
+ const p = 1;
+ const z = 2;
+
+ if (p > z) {
+ assert(1 > 2, "troll");
+ if (p > z) {
+ assert(1 > 2, "troll");
+ if (p > z) {
+ assert(1 > 2, "troll");
+ if (p > z) {
+ assert(1 > 2, "troll");
+ if (p > z) {
+ assert(1 > 2, "troll");
+ if (p > z) {
+ assert(1 > 2, "troll");
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_3.compact b/crates/tree-sitter-compact/test/fixtures/upstream/example_3.compact
new file mode 100644
index 0000000..7f806e4
--- /dev/null
+++ b/crates/tree-sitter-compact/test/fixtures/upstream/example_3.compact
@@ -0,0 +1,41 @@
+// This file is part of Compact.
+// Copyright (C) 2025 Midnight Foundation
+// SPDX-License-Identifier: Apache-2.0
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import CompactStandardLibrary;
+
+pragma language_version >= 0.15;
+
+export circuit method_4(): [] {
+ for (const i of 1..10) {
+
+ assert(1 > 2, "troll");
+ for (const i of 1..10) {
+
+ assert(1 > 2, "troll");
+ for (const i of 1..10) {
+
+ assert(1 > 2, "troll");
+ for (const i of 1..10) {
+
+ assert(1 > 2, "troll");
+
+ }
+
+ }
+
+ }
+
+ }
+}
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_4.compact b/crates/tree-sitter-compact/test/fixtures/upstream/example_4.compact
new file mode 100644
index 0000000..716061b
--- /dev/null
+++ b/crates/tree-sitter-compact/test/fixtures/upstream/example_4.compact
@@ -0,0 +1,32 @@
+// This file is part of Compact.
+// Copyright (C) 2025 Midnight Foundation
+// SPDX-License-Identifier: Apache-2.0
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import CompactStandardLibrary;
+
+export ledger test: Map;
+
+export ledger test2: Map;
+
+export circuit ronan(): [] {
+ return [];
+}
+
+witness caroline(): Field;
+
+export circuit Grendel(x: Field, y: Field, z: Field, d: Uint<32>): Field
+
+ {
+ return x + y + z;
+}
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_5.compact b/crates/tree-sitter-compact/test/fixtures/upstream/example_5.compact
new file mode 100644
index 0000000..881f9b2
--- /dev/null
+++ b/crates/tree-sitter-compact/test/fixtures/upstream/example_5.compact
@@ -0,0 +1,34 @@
+// This file is part of Compact.
+// Copyright (C) 2025 Midnight Foundation
+// SPDX-License-Identifier: Apache-2.0
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import CompactStandardLibrary;
+
+constructor(x: Field, y: Field, z: Field) {
+ const p = x + y + z;
+}
+
+export circuit test(): Field {
+ assert(1 > 2, "hello world");
+}
+
+export circuit test2(): [] {
+
+ const g = default>;
+}
+
+export circuit test3(): Boolean {
+ const p = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,];
+ return [];
+}
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_6.compact b/crates/tree-sitter-compact/test/fixtures/upstream/example_6.compact
new file mode 100644
index 0000000..67d4ac4
--- /dev/null
+++ b/crates/tree-sitter-compact/test/fixtures/upstream/example_6.compact
@@ -0,0 +1,31 @@
+// This file is part of Compact.
+// Copyright (C) 2025 Midnight Foundation
+// SPDX-License-Identifier: Apache-2.0
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import CompactStandardLibrary;
+
+constructor() {
+ const p = default;
+ const z = 1;
+}
+
+export circuit test(): [] {
+ return [];
+}
+
+witness barbara(): Boolean;
+
+witness arnold(x: Float): Boolean;
+
+export ledger counter: Counter;
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_7.compact b/crates/tree-sitter-compact/test/fixtures/upstream/example_7.compact
new file mode 100644
index 0000000..ea7282d
--- /dev/null
+++ b/crates/tree-sitter-compact/test/fixtures/upstream/example_7.compact
@@ -0,0 +1,32 @@
+// This file is part of Compact.
+// Copyright (C) 2025 Midnight Foundation
+// SPDX-License-Identifier: Apache-2.0
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import CompactStandardLibrary;
+
+constructor() {
+ const f = 4;
+ const z = 2;
+ const p = f + z;
+}
+
+export circuit randolph(x: Field, z: Field): Field {
+ return x + z;
+}
+
+witness adam(x: Field, y: Uint<32>): Uint<16>;
+
+export ledger round: Counter;
+
+export ledger ariadna: Map;
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_8.compact b/crates/tree-sitter-compact/test/fixtures/upstream/example_8.compact
new file mode 100644
index 0000000..8280eb9
--- /dev/null
+++ b/crates/tree-sitter-compact/test/fixtures/upstream/example_8.compact
@@ -0,0 +1,43 @@
+// This file is part of Compact.
+// Copyright (C) 2025 Midnight Foundation
+// SPDX-License-Identifier: Apache-2.0
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+pragma language_version > 0.14;
+
+export ledger counter: Counter;
+
+export circuit reynolds(): Field {
+ return default;
+}
+
+constructor(x: Field, y: Field, z: Field) {
+ for (const x of 1..10) {
+ assert(i == i, "hello world");
+ }
+ for (const y of 0..10) {
+ if (y == 1) {
+ assert(y == y,
+ "
+ gorgonzola"
+ );
+ }
+ }
+ return [];
+}
+
+export circuit burt(): [] {
+
+ const y = 1;
+
+}
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_9.compact b/crates/tree-sitter-compact/test/fixtures/upstream/example_9.compact
new file mode 100644
index 0000000..a78e319
--- /dev/null
+++ b/crates/tree-sitter-compact/test/fixtures/upstream/example_9.compact
@@ -0,0 +1,34 @@
+// This file is part of Compact.
+// Copyright (C) 2025 Midnight Foundation
+// SPDX-License-Identifier: Apache-2.0
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import CompactStandardLibrary;
+
+export ledger c: Counter;
+
+export circuit decrement(amount: Uint<16>): [] {
+ return c.decrement(amount);
+}
+
+export circuit increment(amount: Uint<16>): [] {
+ return c.increment(amount);
+}
+
+export circuit reset_to_default(): [] {
+ return c.reset_to_default();
+}
+
+export circuit read(): Uint<64> {
+ return c.read();
+}
diff --git a/crates/tree-sitter-compact/tests/corpus.rs b/crates/tree-sitter-compact/tests/corpus.rs
new file mode 100644
index 0000000..ea03373
--- /dev/null
+++ b/crates/tree-sitter-compact/tests/corpus.rs
@@ -0,0 +1,214 @@
+//! Real Compact sources must parse with no `ERROR` and no `MISSING` node.
+
+use std::path::{Path, PathBuf};
+
+use tree_sitter::{Node, Parser, Tree};
+
+/// Directories that never hold first-party Compact sources.
+const SKIPPED_DIRS: [&str; 5] = ["node_modules", "target", "dist", "build", ".git"];
+
+fn parser() -> Parser {
+ let mut parser = Parser::new();
+ parser
+ .set_language(&tree_sitter_compact::LANGUAGE.into())
+ .expect("the generated parser matches the tree-sitter ABI this crate builds against");
+ parser
+}
+
+fn workspace_root() -> PathBuf {
+ Path::new(env!("CARGO_MANIFEST_DIR"))
+ .join("..")
+ .join("..")
+ .canonicalize()
+ .expect("the crate sits two levels below the workspace root")
+}
+
+fn compact_files(dirs: &[PathBuf]) -> Vec {
+ let mut files = Vec::new();
+ for dir in dirs {
+ collect_compact_files(dir, &mut files);
+ }
+ files.sort();
+ files
+}
+
+fn collect_compact_files(dir: &Path, out: &mut Vec) {
+ let entries = std::fs::read_dir(dir)
+ .unwrap_or_else(|error| panic!("corpus directory {} is readable: {error}", dir.display()));
+
+ for entry in entries {
+ let entry = entry.expect("a directory entry read without error");
+ let path = entry.path();
+ let name = entry.file_name();
+
+ if path.is_dir() {
+ if !SKIPPED_DIRS.iter().any(|skipped| name == *skipped) {
+ collect_compact_files(&path, out);
+ }
+ } else if path
+ .extension()
+ .is_some_and(|extension| extension == "compact")
+ {
+ out.push(path);
+ }
+ }
+}
+
+fn child_kinds(node: Node) -> Vec<&'static str> {
+ let mut cursor = node.walk();
+ let mut kinds = Vec::new();
+ for child in node.children(&mut cursor) {
+ kinds.push(child.kind());
+ }
+ kinds
+}
+
+struct Defect {
+ kind: String,
+ start: usize,
+ end: usize,
+ excerpt: String,
+}
+
+fn excerpt(source: &str, start: usize) -> String {
+ let tail = &source[start..];
+ let mut end = 0;
+ for (index, character) in tail.char_indices().take(120) {
+ end = index + character.len_utf8();
+ }
+ tail[..end].replace('\n', "\\n")
+}
+
+fn find_defect(node: Node, source: &str) -> Option {
+ if node.is_error() || node.is_missing() {
+ let start = node.start_byte();
+ return Some(Defect {
+ kind: if node.is_missing() {
+ format!("MISSING {}", node.kind())
+ } else {
+ node.kind().to_owned()
+ },
+ start,
+ end: node.end_byte(),
+ excerpt: excerpt(source, start),
+ });
+ }
+
+ if !node.has_error() {
+ return None;
+ }
+
+ let mut cursor = node.walk();
+ for child in node.children(&mut cursor) {
+ if let Some(defect) = find_defect(child, source) {
+ return Some(defect);
+ }
+ }
+ None
+}
+
+fn assert_parses_clean(parser: &mut Parser, files: &[PathBuf]) {
+ for path in files {
+ let source = std::fs::read_to_string(path)
+ .unwrap_or_else(|error| panic!("{} is readable UTF-8: {error}", path.display()));
+ let tree: Tree = parser
+ .parse(&source, None)
+ .unwrap_or_else(|| panic!("{} produced a parse tree", path.display()));
+
+ if let Some(defect) = find_defect(tree.root_node(), &source) {
+ let Defect {
+ kind,
+ start,
+ end,
+ excerpt,
+ } = defect;
+ panic!(
+ "{}: {kind} at bytes {start}..{end}\n {excerpt}",
+ path.display()
+ );
+ }
+ }
+}
+
+#[test]
+fn upstream_corpus_parses_without_defects() {
+ let files =
+ compact_files(&[Path::new(env!("CARGO_MANIFEST_DIR")).join("test/fixtures/upstream")]);
+
+ assert!(
+ !files.is_empty(),
+ "the upstream fixture directory is populated"
+ );
+ println!("upstream corpus: {} files", files.len());
+ assert_parses_clean(&mut parser(), &files);
+}
+
+#[test]
+fn in_repo_corpus_parses_without_defects() {
+ let root = workspace_root();
+ let files = compact_files(&[
+ root.join("tests/integrations/fixtures"),
+ root.join("examples"),
+ root.join("packages/simulator/test/fixtures/sample-contracts"),
+ ]);
+
+ assert!(!files.is_empty(), "the repository ships Compact sources");
+ println!("in-repo corpus: {} files", files.len());
+ assert_parses_clean(&mut parser(), &files);
+}
+
+#[test]
+fn external_corpus_parses_without_defects() {
+ let Ok(dir) = std::env::var("COMPACT_CORPUS_DIR") else {
+ println!("external corpus: COMPACT_CORPUS_DIR unset, nothing checked");
+ return;
+ };
+
+ let files = compact_files(&[PathBuf::from(&dir)]);
+
+ assert!(
+ !files.is_empty(),
+ "COMPACT_CORPUS_DIR holds Compact sources"
+ );
+ println!("external corpus: {} files under {dir}", files.len());
+ assert_parses_clean(&mut parser(), &files);
+}
+
+#[test]
+fn doc_commented_export_pure_circuit_exposes_name_and_modifiers() {
+ let source = "/** @description Adds two numbers. */\n\
+ export pure circuit add(a: Uint<32>, b: Uint<32>): Uint<64> {\n\
+ \x20 return a + b;\n\
+ }\n";
+
+ let tree = parser()
+ .parse(source, None)
+ .expect("the snippet produced a parse tree");
+ let root = tree.root_node();
+ assert!(!root.has_error(), "the snippet parsed without defects");
+ assert_eq!(child_kinds(root), ["doc_comment", "circuit_declaration"]);
+
+ let circuit = root.child(1).expect("the circuit follows its doc comment");
+ assert_eq!(
+ child_kinds(circuit),
+ [
+ "export",
+ "pure",
+ "circuit",
+ "identifier",
+ "parameter_list",
+ ":",
+ "type_expression",
+ "block",
+ ]
+ );
+
+ let name = circuit
+ .child_by_field_name("name")
+ .expect("a circuit declaration carries a name field");
+ assert_eq!(
+ name.utf8_text(source.as_bytes())
+ .expect("the source is valid UTF-8"),
+ "add"
+ );
+}
diff --git a/crates/tree-sitter-compact/tree-sitter.json b/crates/tree-sitter-compact/tree-sitter.json
new file mode 100644
index 0000000..f3f86e5
--- /dev/null
+++ b/crates/tree-sitter-compact/tree-sitter.json
@@ -0,0 +1,37 @@
+{
+ "grammars": [
+ {
+ "name": "compact",
+ "camelcase": "Compact",
+ "scope": "source.compact",
+ "path": ".",
+ "file-types": ["compact"],
+ "highlights": ["queries/highlights.scm"],
+ "injection-regex": "^compact$"
+ }
+ ],
+ "metadata": {
+ "version": "0.1.0",
+ "license": "MIT",
+ "description": "Declaration-level Compact grammar for tree-sitter",
+ "authors": [
+ {
+ "name": "OpenZeppelin"
+ }
+ ],
+ "links": {
+ "repository": "https://github.com/OpenZeppelin/compact-tools"
+ }
+ },
+ "bindings": {
+ "c": true,
+ "go": false,
+ "java": false,
+ "kotlin": false,
+ "node": false,
+ "python": false,
+ "rust": true,
+ "swift": false,
+ "zig": false
+ }
+}
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
new file mode 100644
index 0000000..38faa68
--- /dev/null
+++ b/rust-toolchain.toml
@@ -0,0 +1,3 @@
+[toolchain]
+channel = "1.96"
+components = ["rustfmt", "clippy"]
From 0d4991ba96c40c26d540bc2bb855d5f89ab7fe7a Mon Sep 17 00:00:00 2001
From: 0xisk <0xisk@proton.me>
Date: Tue, 15 Sep 2026 09:41:51 +0200
Subject: [PATCH 02/14] refactor(compact-tree-sitter): rename the grammar crate
Every crate in this workspace is named compact-*, so the grammar
crate follows suit instead of the tree-sitter- convention.
The grammar name in tree-sitter.json and the generated C symbol
stay `compact`; only the Cargo package and its directory move.
---
.github/workflows/rust.yml | 4 ++--
Cargo.lock | 18 +++++++++---------
biome.json | 2 +-
.../Cargo.toml | 2 +-
.../README.md | 4 ++--
.../bindings/rust/build.rs | 2 +-
.../bindings/rust/lib.rs | 0
.../grammar.js | 0
.../package.json | 2 +-
.../queries/highlights.scm | 0
.../src/grammar.json | 0
.../src/node-types.json | 0
.../src/parser.c | 0
.../src/tree_sitter/alloc.h | 0
.../src/tree_sitter/array.h | 0
.../src/tree_sitter/parser.h | 0
.../test/corpus/circuits-and-constructors.txt | 0
.../test/corpus/comments.txt | 0
.../test/corpus/imports.txt | 0
.../test/corpus/modules-and-exports.txt | 0
.../test/corpus/pragma-and-include.txt | 0
.../test/corpus/structs-enums-contracts.txt | 0
.../test/corpus/types-ledger-witness.txt | 0
.../test/fixtures/upstream/NOTICE | 0
.../upstream/already_formatted.compact | 0
.../test/fixtures/upstream/example_1.compact | 0
.../test/fixtures/upstream/example_10.compact | 0
.../test/fixtures/upstream/example_11.compact | 0
.../test/fixtures/upstream/example_12.compact | 0
.../test/fixtures/upstream/example_13.compact | 0
.../test/fixtures/upstream/example_2.compact | 0
.../test/fixtures/upstream/example_3.compact | 0
.../test/fixtures/upstream/example_4.compact | 0
.../test/fixtures/upstream/example_5.compact | 0
.../test/fixtures/upstream/example_6.compact | 0
.../test/fixtures/upstream/example_7.compact | 0
.../test/fixtures/upstream/example_8.compact | 0
.../test/fixtures/upstream/example_9.compact | 0
.../tests/corpus.rs | 2 +-
.../tree-sitter.json | 0
40 files changed, 18 insertions(+), 18 deletions(-)
rename crates/{tree-sitter-compact => compact-tree-sitter}/Cargo.toml (95%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/README.md (97%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/bindings/rust/build.rs (93%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/bindings/rust/lib.rs (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/grammar.js (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/package.json (89%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/queries/highlights.scm (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/src/grammar.json (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/src/node-types.json (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/src/parser.c (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/src/tree_sitter/alloc.h (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/src/tree_sitter/array.h (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/src/tree_sitter/parser.h (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/corpus/circuits-and-constructors.txt (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/corpus/comments.txt (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/corpus/imports.txt (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/corpus/modules-and-exports.txt (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/corpus/pragma-and-include.txt (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/corpus/structs-enums-contracts.txt (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/corpus/types-ledger-witness.txt (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/fixtures/upstream/NOTICE (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/fixtures/upstream/already_formatted.compact (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/fixtures/upstream/example_1.compact (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/fixtures/upstream/example_10.compact (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/fixtures/upstream/example_11.compact (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/fixtures/upstream/example_12.compact (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/fixtures/upstream/example_13.compact (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/fixtures/upstream/example_2.compact (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/fixtures/upstream/example_3.compact (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/fixtures/upstream/example_4.compact (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/fixtures/upstream/example_5.compact (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/fixtures/upstream/example_6.compact (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/fixtures/upstream/example_7.compact (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/fixtures/upstream/example_8.compact (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/test/fixtures/upstream/example_9.compact (100%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/tests/corpus.rs (99%)
rename crates/{tree-sitter-compact => compact-tree-sitter}/tree-sitter.json (100%)
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 2bc8283..a3f227a 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -39,8 +39,8 @@ jobs:
run: cargo test --workspace
- name: Check the generated parser is up to date
- working-directory: crates/tree-sitter-compact
+ working-directory: crates/compact-tree-sitter
run: npx -y tree-sitter-cli@0.25.8 generate
- name: Fail if regeneration changed the parser
- run: git diff --exit-code -- crates/tree-sitter-compact/src
+ run: git diff --exit-code -- crates/compact-tree-sitter/src
diff --git a/Cargo.lock b/Cargo.lock
index e78a9e5..f1a0406 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -21,6 +21,15 @@ dependencies = [
"shlex",
]
+[[package]]
+name = "compact-tree-sitter"
+version = "0.1.0"
+dependencies = [
+ "cc",
+ "tree-sitter",
+ "tree-sitter-language",
+]
+
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -188,15 +197,6 @@ dependencies = [
"tree-sitter-language",
]
-[[package]]
-name = "tree-sitter-compact"
-version = "0.1.0"
-dependencies = [
- "cc",
- "tree-sitter",
- "tree-sitter-language",
-]
-
[[package]]
name = "tree-sitter-language"
version = "0.1.8"
diff --git a/biome.json b/biome.json
index 88b7ec7..045f84c 100644
--- a/biome.json
+++ b/biome.json
@@ -13,7 +13,7 @@
"!**/tsconfig.base.json",
"!**/tsconfig*.json",
"!**/*.compact",
- "!crates/tree-sitter-compact/src/**/*",
+ "!crates/compact-tree-sitter/src/**/*",
"!**/artifacts/**/*",
"!**/coverage/**/*",
"!**/dist/**/*",
diff --git a/crates/tree-sitter-compact/Cargo.toml b/crates/compact-tree-sitter/Cargo.toml
similarity index 95%
rename from crates/tree-sitter-compact/Cargo.toml
rename to crates/compact-tree-sitter/Cargo.toml
index 3626181..8ae71a8 100644
--- a/crates/tree-sitter-compact/Cargo.toml
+++ b/crates/compact-tree-sitter/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "tree-sitter-compact"
+name = "compact-tree-sitter"
version = "0.1.0"
description = "Declaration-level Compact grammar for tree-sitter"
build = "bindings/rust/build.rs"
diff --git a/crates/tree-sitter-compact/README.md b/crates/compact-tree-sitter/README.md
similarity index 97%
rename from crates/tree-sitter-compact/README.md
rename to crates/compact-tree-sitter/README.md
index 287c198..50f0d70 100644
--- a/crates/tree-sitter-compact/README.md
+++ b/crates/compact-tree-sitter/README.md
@@ -1,4 +1,4 @@
-# tree-sitter-compact
+# compact-tree-sitter
Declaration-level [tree-sitter](https://tree-sitter.github.io) grammar for the
Compact smart-contract language (Midnight), tracking language version 0.26.
@@ -68,7 +68,7 @@ are generated and committed. After editing `grammar.js`:
npx -y tree-sitter-cli@0.25.8 generate
```
-CI regenerates and fails on any diff under `crates/tree-sitter-compact/src`.
+CI regenerates and fails on any diff under `crates/compact-tree-sitter/src`.
## Tests
diff --git a/crates/tree-sitter-compact/bindings/rust/build.rs b/crates/compact-tree-sitter/bindings/rust/build.rs
similarity index 93%
rename from crates/tree-sitter-compact/bindings/rust/build.rs
rename to crates/compact-tree-sitter/bindings/rust/build.rs
index c65afd8..603d5b7 100644
--- a/crates/tree-sitter-compact/bindings/rust/build.rs
+++ b/crates/compact-tree-sitter/bindings/rust/build.rs
@@ -10,7 +10,7 @@ fn main() {
config.flag_if_supported("-Wno-unused-parameter");
config.flag_if_supported("-Wno-trigraphs");
- config.compile("tree-sitter-compact");
+ config.compile("compact-tree-sitter");
println!("cargo:rerun-if-changed=src/parser.c");
println!("cargo:rerun-if-changed=src/grammar.json");
diff --git a/crates/tree-sitter-compact/bindings/rust/lib.rs b/crates/compact-tree-sitter/bindings/rust/lib.rs
similarity index 100%
rename from crates/tree-sitter-compact/bindings/rust/lib.rs
rename to crates/compact-tree-sitter/bindings/rust/lib.rs
diff --git a/crates/tree-sitter-compact/grammar.js b/crates/compact-tree-sitter/grammar.js
similarity index 100%
rename from crates/tree-sitter-compact/grammar.js
rename to crates/compact-tree-sitter/grammar.js
diff --git a/crates/tree-sitter-compact/package.json b/crates/compact-tree-sitter/package.json
similarity index 89%
rename from crates/tree-sitter-compact/package.json
rename to crates/compact-tree-sitter/package.json
index 7f85f18..2760c2c 100644
--- a/crates/tree-sitter-compact/package.json
+++ b/crates/compact-tree-sitter/package.json
@@ -1,5 +1,5 @@
{
- "name": "tree-sitter-compact",
+ "name": "compact-tree-sitter",
"version": "0.1.0",
"private": true,
"description": "Declaration-level Compact grammar for tree-sitter",
diff --git a/crates/tree-sitter-compact/queries/highlights.scm b/crates/compact-tree-sitter/queries/highlights.scm
similarity index 100%
rename from crates/tree-sitter-compact/queries/highlights.scm
rename to crates/compact-tree-sitter/queries/highlights.scm
diff --git a/crates/tree-sitter-compact/src/grammar.json b/crates/compact-tree-sitter/src/grammar.json
similarity index 100%
rename from crates/tree-sitter-compact/src/grammar.json
rename to crates/compact-tree-sitter/src/grammar.json
diff --git a/crates/tree-sitter-compact/src/node-types.json b/crates/compact-tree-sitter/src/node-types.json
similarity index 100%
rename from crates/tree-sitter-compact/src/node-types.json
rename to crates/compact-tree-sitter/src/node-types.json
diff --git a/crates/tree-sitter-compact/src/parser.c b/crates/compact-tree-sitter/src/parser.c
similarity index 100%
rename from crates/tree-sitter-compact/src/parser.c
rename to crates/compact-tree-sitter/src/parser.c
diff --git a/crates/tree-sitter-compact/src/tree_sitter/alloc.h b/crates/compact-tree-sitter/src/tree_sitter/alloc.h
similarity index 100%
rename from crates/tree-sitter-compact/src/tree_sitter/alloc.h
rename to crates/compact-tree-sitter/src/tree_sitter/alloc.h
diff --git a/crates/tree-sitter-compact/src/tree_sitter/array.h b/crates/compact-tree-sitter/src/tree_sitter/array.h
similarity index 100%
rename from crates/tree-sitter-compact/src/tree_sitter/array.h
rename to crates/compact-tree-sitter/src/tree_sitter/array.h
diff --git a/crates/tree-sitter-compact/src/tree_sitter/parser.h b/crates/compact-tree-sitter/src/tree_sitter/parser.h
similarity index 100%
rename from crates/tree-sitter-compact/src/tree_sitter/parser.h
rename to crates/compact-tree-sitter/src/tree_sitter/parser.h
diff --git a/crates/tree-sitter-compact/test/corpus/circuits-and-constructors.txt b/crates/compact-tree-sitter/test/corpus/circuits-and-constructors.txt
similarity index 100%
rename from crates/tree-sitter-compact/test/corpus/circuits-and-constructors.txt
rename to crates/compact-tree-sitter/test/corpus/circuits-and-constructors.txt
diff --git a/crates/tree-sitter-compact/test/corpus/comments.txt b/crates/compact-tree-sitter/test/corpus/comments.txt
similarity index 100%
rename from crates/tree-sitter-compact/test/corpus/comments.txt
rename to crates/compact-tree-sitter/test/corpus/comments.txt
diff --git a/crates/tree-sitter-compact/test/corpus/imports.txt b/crates/compact-tree-sitter/test/corpus/imports.txt
similarity index 100%
rename from crates/tree-sitter-compact/test/corpus/imports.txt
rename to crates/compact-tree-sitter/test/corpus/imports.txt
diff --git a/crates/tree-sitter-compact/test/corpus/modules-and-exports.txt b/crates/compact-tree-sitter/test/corpus/modules-and-exports.txt
similarity index 100%
rename from crates/tree-sitter-compact/test/corpus/modules-and-exports.txt
rename to crates/compact-tree-sitter/test/corpus/modules-and-exports.txt
diff --git a/crates/tree-sitter-compact/test/corpus/pragma-and-include.txt b/crates/compact-tree-sitter/test/corpus/pragma-and-include.txt
similarity index 100%
rename from crates/tree-sitter-compact/test/corpus/pragma-and-include.txt
rename to crates/compact-tree-sitter/test/corpus/pragma-and-include.txt
diff --git a/crates/tree-sitter-compact/test/corpus/structs-enums-contracts.txt b/crates/compact-tree-sitter/test/corpus/structs-enums-contracts.txt
similarity index 100%
rename from crates/tree-sitter-compact/test/corpus/structs-enums-contracts.txt
rename to crates/compact-tree-sitter/test/corpus/structs-enums-contracts.txt
diff --git a/crates/tree-sitter-compact/test/corpus/types-ledger-witness.txt b/crates/compact-tree-sitter/test/corpus/types-ledger-witness.txt
similarity index 100%
rename from crates/tree-sitter-compact/test/corpus/types-ledger-witness.txt
rename to crates/compact-tree-sitter/test/corpus/types-ledger-witness.txt
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/NOTICE b/crates/compact-tree-sitter/test/fixtures/upstream/NOTICE
similarity index 100%
rename from crates/tree-sitter-compact/test/fixtures/upstream/NOTICE
rename to crates/compact-tree-sitter/test/fixtures/upstream/NOTICE
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/already_formatted.compact b/crates/compact-tree-sitter/test/fixtures/upstream/already_formatted.compact
similarity index 100%
rename from crates/tree-sitter-compact/test/fixtures/upstream/already_formatted.compact
rename to crates/compact-tree-sitter/test/fixtures/upstream/already_formatted.compact
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_1.compact b/crates/compact-tree-sitter/test/fixtures/upstream/example_1.compact
similarity index 100%
rename from crates/tree-sitter-compact/test/fixtures/upstream/example_1.compact
rename to crates/compact-tree-sitter/test/fixtures/upstream/example_1.compact
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_10.compact b/crates/compact-tree-sitter/test/fixtures/upstream/example_10.compact
similarity index 100%
rename from crates/tree-sitter-compact/test/fixtures/upstream/example_10.compact
rename to crates/compact-tree-sitter/test/fixtures/upstream/example_10.compact
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_11.compact b/crates/compact-tree-sitter/test/fixtures/upstream/example_11.compact
similarity index 100%
rename from crates/tree-sitter-compact/test/fixtures/upstream/example_11.compact
rename to crates/compact-tree-sitter/test/fixtures/upstream/example_11.compact
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_12.compact b/crates/compact-tree-sitter/test/fixtures/upstream/example_12.compact
similarity index 100%
rename from crates/tree-sitter-compact/test/fixtures/upstream/example_12.compact
rename to crates/compact-tree-sitter/test/fixtures/upstream/example_12.compact
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_13.compact b/crates/compact-tree-sitter/test/fixtures/upstream/example_13.compact
similarity index 100%
rename from crates/tree-sitter-compact/test/fixtures/upstream/example_13.compact
rename to crates/compact-tree-sitter/test/fixtures/upstream/example_13.compact
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_2.compact b/crates/compact-tree-sitter/test/fixtures/upstream/example_2.compact
similarity index 100%
rename from crates/tree-sitter-compact/test/fixtures/upstream/example_2.compact
rename to crates/compact-tree-sitter/test/fixtures/upstream/example_2.compact
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_3.compact b/crates/compact-tree-sitter/test/fixtures/upstream/example_3.compact
similarity index 100%
rename from crates/tree-sitter-compact/test/fixtures/upstream/example_3.compact
rename to crates/compact-tree-sitter/test/fixtures/upstream/example_3.compact
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_4.compact b/crates/compact-tree-sitter/test/fixtures/upstream/example_4.compact
similarity index 100%
rename from crates/tree-sitter-compact/test/fixtures/upstream/example_4.compact
rename to crates/compact-tree-sitter/test/fixtures/upstream/example_4.compact
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_5.compact b/crates/compact-tree-sitter/test/fixtures/upstream/example_5.compact
similarity index 100%
rename from crates/tree-sitter-compact/test/fixtures/upstream/example_5.compact
rename to crates/compact-tree-sitter/test/fixtures/upstream/example_5.compact
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_6.compact b/crates/compact-tree-sitter/test/fixtures/upstream/example_6.compact
similarity index 100%
rename from crates/tree-sitter-compact/test/fixtures/upstream/example_6.compact
rename to crates/compact-tree-sitter/test/fixtures/upstream/example_6.compact
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_7.compact b/crates/compact-tree-sitter/test/fixtures/upstream/example_7.compact
similarity index 100%
rename from crates/tree-sitter-compact/test/fixtures/upstream/example_7.compact
rename to crates/compact-tree-sitter/test/fixtures/upstream/example_7.compact
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_8.compact b/crates/compact-tree-sitter/test/fixtures/upstream/example_8.compact
similarity index 100%
rename from crates/tree-sitter-compact/test/fixtures/upstream/example_8.compact
rename to crates/compact-tree-sitter/test/fixtures/upstream/example_8.compact
diff --git a/crates/tree-sitter-compact/test/fixtures/upstream/example_9.compact b/crates/compact-tree-sitter/test/fixtures/upstream/example_9.compact
similarity index 100%
rename from crates/tree-sitter-compact/test/fixtures/upstream/example_9.compact
rename to crates/compact-tree-sitter/test/fixtures/upstream/example_9.compact
diff --git a/crates/tree-sitter-compact/tests/corpus.rs b/crates/compact-tree-sitter/tests/corpus.rs
similarity index 99%
rename from crates/tree-sitter-compact/tests/corpus.rs
rename to crates/compact-tree-sitter/tests/corpus.rs
index ea03373..7ff16ed 100644
--- a/crates/tree-sitter-compact/tests/corpus.rs
+++ b/crates/compact-tree-sitter/tests/corpus.rs
@@ -10,7 +10,7 @@ const SKIPPED_DIRS: [&str; 5] = ["node_modules", "target", "dist", "build", ".gi
fn parser() -> Parser {
let mut parser = Parser::new();
parser
- .set_language(&tree_sitter_compact::LANGUAGE.into())
+ .set_language(&compact_tree_sitter::LANGUAGE.into())
.expect("the generated parser matches the tree-sitter ABI this crate builds against");
parser
}
diff --git a/crates/tree-sitter-compact/tree-sitter.json b/crates/compact-tree-sitter/tree-sitter.json
similarity index 100%
rename from crates/tree-sitter-compact/tree-sitter.json
rename to crates/compact-tree-sitter/tree-sitter.json
From ec5cb8a0e5d21cfe11d7a78870ce45b8e994d4ff Mon Sep 17 00:00:00 2001
From: 0xisk <0xisk@proton.me>
Date: Tue, 15 Sep 2026 09:43:29 +0200
Subject: [PATCH 03/14] feat(compact-lint): add the check command
Adds the compact-lint CLI with one subcommand, check, that enforces
per-declaration-kind doc-comment templates from compact-lint.toml and
optionally runs compact format --check. Findings print one per line
as path:line:col: rule: message on stdout; exit 1 on findings, 2 on
any error. fix and fill-constraints come later and only add tables
to the same toml.
* doc: tags parsed into an ordered list with line and column, so a
finding points at the @ of the offending tag.
* rules: missing-doc, missing-tag, forbidden-tag, module-name,
missing-constraints, constraints-format, constraints-placeholder
(--strict), parse, format. Constraints apply to exported non-pure
circuits only.
* config: every field optional, unknown keys and tags without their @
rejected; an explicit --config is a preset anchored at the cwd.
* tests: one fixture case per rule through the binary, plus unit
tests per module.
---
Cargo.lock | 365 +++++++++++++++
Cargo.toml | 10 +
crates/compact-lint/Cargo.toml | 33 ++
crates/compact-lint/README.md | 128 ++++++
.../examples/compact-contracts.toml | 40 ++
crates/compact-lint/src/check.rs | 117 +++++
crates/compact-lint/src/config.rs | 305 +++++++++++++
crates/compact-lint/src/discover.rs | 114 +++++
crates/compact-lint/src/doc.rs | 211 +++++++++
crates/compact-lint/src/format.rs | 156 +++++++
crates/compact-lint/src/lib.rs | 16 +
crates/compact-lint/src/main.rs | 114 +++++
crates/compact-lint/src/model.rs | 177 ++++++++
crates/compact-lint/src/report.rs | 142 ++++++
crates/compact-lint/src/rules.rs | 415 ++++++++++++++++++
crates/compact-lint/tests/check.rs | 172 ++++++++
.../tests/fixtures/clean/Clean.compact | 56 +++
.../tests/fixtures/clean/compact-lint.toml | 27 ++
.../tests/fixtures/clean/expected.txt | 0
.../constraints-format/Malformed.compact | 17 +
.../constraints-format/compact-lint.toml | 27 ++
.../fixtures/constraints-format/expected.txt | 2 +
.../constraints-placeholder/Unfilled.compact | 11 +
.../constraints-placeholder/compact-lint.toml | 27 ++
.../constraints-placeholder/expected.txt | 1 +
.../fixtures/forbidden-tag/Returns.compact | 18 +
.../fixtures/forbidden-tag/compact-lint.toml | 27 ++
.../tests/fixtures/forbidden-tag/expected.txt | 2 +
.../missing-constraints/Unmeasured.compact | 17 +
.../missing-constraints/compact-lint.toml | 27 ++
.../fixtures/missing-constraints/expected.txt | 1 +
.../tests/fixtures/missing-doc/Gaps.compact | 20 +
.../fixtures/missing-doc/compact-lint.toml | 27 ++
.../tests/fixtures/missing-doc/expected.txt | 3 +
.../fixtures/missing-tag/Untagged.compact | 10 +
.../fixtures/missing-tag/compact-lint.toml | 27 ++
.../tests/fixtures/missing-tag/expected.txt | 2 +
.../fixtures/module-name/Renamed.compact | 6 +
.../fixtures/module-name/compact-lint.toml | 27 ++
.../tests/fixtures/module-name/expected.txt | 1 +
.../tests/fixtures/parse/Broken.compact | 3 +
.../tests/fixtures/parse/compact-lint.toml | 2 +
.../tests/fixtures/parse/expected.txt | 1 +
43 files changed, 2904 insertions(+)
create mode 100644 crates/compact-lint/Cargo.toml
create mode 100644 crates/compact-lint/README.md
create mode 100644 crates/compact-lint/examples/compact-contracts.toml
create mode 100644 crates/compact-lint/src/check.rs
create mode 100644 crates/compact-lint/src/config.rs
create mode 100644 crates/compact-lint/src/discover.rs
create mode 100644 crates/compact-lint/src/doc.rs
create mode 100644 crates/compact-lint/src/format.rs
create mode 100644 crates/compact-lint/src/lib.rs
create mode 100644 crates/compact-lint/src/main.rs
create mode 100644 crates/compact-lint/src/model.rs
create mode 100644 crates/compact-lint/src/report.rs
create mode 100644 crates/compact-lint/src/rules.rs
create mode 100644 crates/compact-lint/tests/check.rs
create mode 100644 crates/compact-lint/tests/fixtures/clean/Clean.compact
create mode 100644 crates/compact-lint/tests/fixtures/clean/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/clean/expected.txt
create mode 100644 crates/compact-lint/tests/fixtures/constraints-format/Malformed.compact
create mode 100644 crates/compact-lint/tests/fixtures/constraints-format/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/constraints-format/expected.txt
create mode 100644 crates/compact-lint/tests/fixtures/constraints-placeholder/Unfilled.compact
create mode 100644 crates/compact-lint/tests/fixtures/constraints-placeholder/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/constraints-placeholder/expected.txt
create mode 100644 crates/compact-lint/tests/fixtures/forbidden-tag/Returns.compact
create mode 100644 crates/compact-lint/tests/fixtures/forbidden-tag/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/forbidden-tag/expected.txt
create mode 100644 crates/compact-lint/tests/fixtures/missing-constraints/Unmeasured.compact
create mode 100644 crates/compact-lint/tests/fixtures/missing-constraints/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/missing-constraints/expected.txt
create mode 100644 crates/compact-lint/tests/fixtures/missing-doc/Gaps.compact
create mode 100644 crates/compact-lint/tests/fixtures/missing-doc/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/missing-doc/expected.txt
create mode 100644 crates/compact-lint/tests/fixtures/missing-tag/Untagged.compact
create mode 100644 crates/compact-lint/tests/fixtures/missing-tag/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/missing-tag/expected.txt
create mode 100644 crates/compact-lint/tests/fixtures/module-name/Renamed.compact
create mode 100644 crates/compact-lint/tests/fixtures/module-name/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/module-name/expected.txt
create mode 100644 crates/compact-lint/tests/fixtures/parse/Broken.compact
create mode 100644 crates/compact-lint/tests/fixtures/parse/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/parse/expected.txt
diff --git a/Cargo.lock b/Cargo.lock
index f1a0406..80f353a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -11,6 +11,88 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "anstream"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
+name = "anstyle-parse"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
+
+[[package]]
+name = "assert_cmd"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6"
+dependencies = [
+ "anstyle",
+ "bstr",
+ "libc",
+ "predicates",
+ "predicates-core",
+ "predicates-tree",
+ "wait-timeout",
+]
+
+[[package]]
+name = "bstr"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
+dependencies = [
+ "memchr",
+ "regex-automata",
+ "serde_core",
+]
+
[[package]]
name = "cc"
version = "1.4.6"
@@ -21,6 +103,68 @@ dependencies = [
"shlex",
]
+[[package]]
+name = "clap"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
+
+[[package]]
+name = "compact-lint"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "assert_cmd",
+ "clap",
+ "compact-tree-sitter",
+ "globset",
+ "serde",
+ "thiserror",
+ "toml",
+ "tree-sitter",
+ "walkdir",
+]
+
[[package]]
name = "compact-tree-sitter"
version = "0.1.0"
@@ -30,6 +174,12 @@ dependencies = [
"tree-sitter-language",
]
+[[package]]
+name = "difflib"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
+
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -42,12 +192,31 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
+[[package]]
+name = "globset"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "log",
+ "regex-automata",
+ "regex-syntax",
+]
+
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
[[package]]
name = "indexmap"
version = "2.14.2"
@@ -58,18 +227,69 @@ dependencies = [
"hashbrown",
]
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
+
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+[[package]]
+name = "libc"
+version = "0.2.189"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+
+[[package]]
+name = "log"
+version = "0.4.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
+
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+
+[[package]]
+name = "predicates"
+version = "3.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe"
+dependencies = [
+ "anstyle",
+ "difflib",
+ "predicates-core",
+]
+
+[[package]]
+name = "predicates-core"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144"
+
+[[package]]
+name = "predicates-tree"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2"
+dependencies = [
+ "predicates-core",
+ "termtree",
+]
+
[[package]]
name = "proc-macro2"
version = "1.0.107"
@@ -117,6 +337,15 @@ version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
[[package]]
name = "serde"
version = "1.0.229"
@@ -124,6 +353,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
+ "serde_derive",
]
[[package]]
@@ -160,6 +390,15 @@ dependencies = [
"zmij",
]
+[[package]]
+name = "serde_spanned"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
+dependencies = [
+ "serde_core",
+]
+
[[package]]
name = "shlex"
version = "2.0.1"
@@ -172,6 +411,12 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
[[package]]
name = "syn"
version = "3.0.5"
@@ -183,6 +428,71 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "termtree"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
+
+[[package]]
+name = "thiserror"
+version = "2.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "toml"
+version = "1.1.6+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a"
+dependencies = [
+ "indexmap",
+ "serde_core",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_parser",
+ "toml_writer",
+ "winnow",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "1.1.1+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.1.3+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
+dependencies = [
+ "winnow",
+]
+
+[[package]]
+name = "toml_writer"
+version = "1.1.2+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
+
[[package]]
name = "tree-sitter"
version = "0.25.10"
@@ -209,6 +519,61 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "wait-timeout"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "winnow"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
+
[[package]]
name = "zmij"
version = "1.0.23"
diff --git a/Cargo.toml b/Cargo.toml
index af07435..5e096be 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,6 +12,16 @@ rust-version = "1.96"
pedantic = { level = "warn", priority = -1 }
[workspace.dependencies]
+anyhow = "1.0"
cc = "1"
+clap = { version = "4.6", features = ["derive", "env"] }
+globset = "0.4"
+serde = { version = "1.0", features = ["derive"] }
+thiserror = "2.0"
+toml = "1.1"
tree-sitter = "0.25"
+compact-tree-sitter = { version = "0.1.0", path = "crates/compact-tree-sitter" }
tree-sitter-language = "0.1"
+walkdir = "2.5"
+
+assert_cmd = "2.2"
diff --git a/crates/compact-lint/Cargo.toml b/crates/compact-lint/Cargo.toml
new file mode 100644
index 0000000..0983475
--- /dev/null
+++ b/crates/compact-lint/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "compact-lint"
+version = "0.1.0"
+description = "Doc-comment linter for Compact sources"
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+rust-version.workspace = true
+
+[lints]
+workspace = true
+
+[lib]
+name = "compact_lint"
+path = "src/lib.rs"
+
+[[bin]]
+name = "compact-lint"
+path = "src/main.rs"
+
+[dependencies]
+anyhow.workspace = true
+clap.workspace = true
+globset.workspace = true
+serde.workspace = true
+thiserror.workspace = true
+toml.workspace = true
+tree-sitter.workspace = true
+compact-tree-sitter.workspace = true
+walkdir.workspace = true
+
+[dev-dependencies]
+assert_cmd.workspace = true
diff --git a/crates/compact-lint/README.md b/crates/compact-lint/README.md
new file mode 100644
index 0000000..28bd628
--- /dev/null
+++ b/crates/compact-lint/README.md
@@ -0,0 +1,128 @@
+# compact-lint
+
+Doc-comment linter for Compact sources. It parses `.compact` files with
+[`compact-tree-sitter`](../compact-tree-sitter), matches every declaration against the
+per-kind template in `compact-lint.toml`, and prints one finding per line.
+
+## Usage
+
+```sh
+compact-lint check [PATHS]... [--config ] [--strict] [--no-format] [--compact-bin ]
+```
+
+- `PATHS` — files or directories, walked recursively for `.compact`. Skipped while
+ walking: `node_modules`, `target`, `dist`, `build`, `.git`.
+- No `PATHS` — the config's `include` globs are used instead.
+- `--config ` — use this config instead of searching upward for
+ `compact-lint.toml`.
+- `--strict` — turn `k=?` / `rows=?` placeholders into findings. Use it on release
+ branches.
+- `--no-format` — skip the `compact format --check` pass. Required where the `compact`
+ binary is unavailable, CI included.
+- `--compact-bin ` — path to the `compact` binary. Also settable with
+ `COMPACT_LINT_COMPACT_BIN`; the flag wins.
+
+Findings go to stdout, one per line, in `path:line:col: rule: message` form (the
+rustc / eslint shape, so editors and `grep` both read it). The `N findings in M files`
+summary goes to stderr, where `M` is the number of files checked.
+
+## Exit codes
+
+- `0` — no findings.
+- `1` — findings.
+- `2` — usage, config, IO or parser error.
+
+## Rules
+
+| Rule | Meaning |
+| --- | --- |
+| `missing-doc` | The declaration needs docs per `kinds..docs` and has no doc comment attached. |
+| `missing-tag` | A tag listed in `kinds..tags` is absent from the doc comment. |
+| `forbidden-tag` | A tag listed in `tags.forbid` is present. |
+| `module-name` | `@module ` does not name the module it documents. |
+| `missing-constraints` | An exported non-pure circuit has no constraints tag. |
+| `constraints-format` | The constraints value is not `k=, rows=`. |
+| `constraints-placeholder` | `--strict` only: the constraints value still holds a `?`. |
+| `parse` | The file holds a syntax defect; the first one is reported and the doc rules are skipped for that file. |
+| `format` | `compact format --check` reported the file. |
+
+Rule detail:
+
+- **Attachment.** A doc comment counts as attached only when it is the declaration's
+ immediately preceding *named* sibling. A `//` or `/* */` comment between the two
+ breaks the attachment and the declaration reads as undocumented.
+- **Export.** "Exported" means the declaration carries the `export` token itself. A
+ non-exported circuit inside an exported module is not exported.
+- **Constraints.** The rule applies to exported non-pure circuits only, and that is
+ fixed, not configurable. A missing doc comment reports as `missing-doc`, not
+ `missing-constraints`.
+- **`module-name`.** The documented name is the first word of the `@module` value,
+ without its generic arguments or trailing punctuation, so `@module Signer` matches
+ `module Signer` and `@module Utils.` matches `module Utils`.
+- **`forbidden-tag`.** It fires on any doc comment attached to a checked declaration,
+ including declarations whose docs are optional.
+
+## Config
+
+`compact-lint.toml`, searched for upward from the current directory. Every field is
+optional; unknown fields are rejected. A config found by walking upward anchors the
+`include` globs at its own directory; a config named with `--config` is a shareable
+preset, so the globs stay anchored at the current directory.
+
+```toml
+version = 1
+include = ["contracts/src/**/*.compact"]
+exclude = ["**/test/mocks/**", "**/archive/**"]
+
+[constraints]
+tag = "@constraints"
+
+[tags]
+forbid = ["@return"]
+
+[kinds.module]
+docs = "exported"
+tags = ["@module", "@description"]
+```
+
+Defaults when no config file is found:
+
+| Key | Default |
+| --- | --- |
+| `version` | `1`, the only version this build accepts |
+| `include` | `["**/*.compact"]` |
+| `exclude` | `[]` |
+| `constraints.tag` | `"@constraints"` |
+| `tags.forbid` | `[]` |
+| `kinds..docs` | `"exported"` |
+| `kinds..tags` | `[]` |
+
+- `kinds` takes one table per kind: `module`, `circuit`, `ledger`, `witness`,
+ `constructor`, `struct`, `enum`, `contract`, `type`. `pragma`, `import`, `include`,
+ `export { … }` and `contract implements` are never checked.
+- `docs` is `"all"`, `"exported"` or `"none"`.
+- `constructor` carries no `export` token, so `"exported"` means "never" for it. Use
+ `"all"` or `"none"` there.
+- `exclude` filters files found by walking a directory. A file named on the command
+ line is always checked.
+
+`examples/compact-contracts.toml` is the config for OpenZeppelin/compact-contracts.
+
+## Doc-comment model
+
+- `/**`, `*/` and the leading ` * ` gutter are stripped.
+- A tag line's first non-blank token is `@` followed by one or more ASCII letters.
+- A tag's value is the rest of its line plus every following line until the next tag or
+ a blank line.
+- Repeated tags are kept in source order; `@param` normally repeats.
+- `@param {Type} name - text` is not parsed further.
+
+## Tests
+
+```sh
+cargo test --workspace
+```
+
+`tests/fixtures//` holds one case per rule plus a clean case: a
+`compact-lint.toml`, the `.compact` sources, and `expected.txt`, the exact stdout with
+paths relative to the case directory.
diff --git a/crates/compact-lint/examples/compact-contracts.toml b/crates/compact-lint/examples/compact-contracts.toml
new file mode 100644
index 0000000..402186d
--- /dev/null
+++ b/crates/compact-lint/examples/compact-contracts.toml
@@ -0,0 +1,40 @@
+# compact-lint config for OpenZeppelin/compact-contracts.
+# Copy it to the repository root as compact-lint.toml.
+
+version = 1
+include = ["contracts/src/**/*.compact"]
+exclude = ["**/test/mocks/**", "**/archive/**"]
+
+# `@constraints` is the default; set `tag` to point the rule at another annotation.
+[constraints]
+tag = "@constraints"
+
+[tags]
+forbid = ["@return"]
+
+[kinds.module]
+docs = "exported"
+tags = ["@module", "@description"]
+
+[kinds.circuit]
+tags = ["@description"]
+
+[kinds.ledger]
+tags = ["@description"]
+
+[kinds.witness]
+docs = "all"
+tags = ["@description"]
+
+[kinds.struct]
+tags = ["@description"]
+
+[kinds.enum]
+tags = ["@description"]
+
+[kinds.constructor]
+docs = "none"
+
+[kinds.contract]
+
+[kinds.type]
diff --git a/crates/compact-lint/src/check.rs b/crates/compact-lint/src/check.rs
new file mode 100644
index 0000000..2f2d2a8
--- /dev/null
+++ b/crates/compact-lint/src/check.rs
@@ -0,0 +1,117 @@
+//! The `check` subcommand: resolve config, collect files, run the rules.
+
+use std::ffi::OsString;
+use std::path::{Path, PathBuf};
+
+use thiserror::Error;
+
+use crate::config::{Config, ConfigError, discover as discover_config};
+use crate::discover::{self, DiscoverError};
+use crate::format::{self, FormatError};
+use crate::report::Finding;
+use crate::rules::{LintError, Linter};
+
+#[derive(Debug, Error)]
+pub enum CheckError {
+ #[error(transparent)]
+ Config(#[from] ConfigError),
+ #[error(transparent)]
+ Discover(#[from] DiscoverError),
+ #[error(transparent)]
+ Lint(#[from] LintError),
+ #[error(transparent)]
+ Format(#[from] FormatError),
+ #[error("cannot read {path}")]
+ Read {
+ path: PathBuf,
+ #[source]
+ source: std::io::Error,
+ },
+}
+
+/// Everything the `check` subcommand needs, already resolved from flags and env.
+pub struct Options {
+ pub paths: Vec,
+ pub config_path: Option,
+ pub strict: bool,
+ pub no_format: bool,
+ pub compact_bin: OsString,
+}
+
+/// The findings, plus how many files produced them.
+pub struct Outcome {
+ pub findings: Vec,
+ pub files_checked: usize,
+}
+
+impl Outcome {
+ #[must_use]
+ pub fn summary(&self) -> String {
+ format!(
+ "{} findings in {} files",
+ self.findings.len(),
+ self.files_checked
+ )
+ }
+}
+
+/// Runs the subcommand with `cwd` as the working directory.
+/// # Errors
+/// Returns an error when the config, the file walk, the parser or the formatter fails.
+pub fn run(options: &Options, cwd: &Path) -> Result {
+ let (config, config_path, base) = resolve_config(options, cwd)?;
+ let source = config_path.as_deref().unwrap_or(Path::new(""));
+
+ let exclude = config.exclude_set(source)?;
+ let files = if options.paths.is_empty() {
+ let include = config.include_set(source)?;
+ discover::from_globs(&base, &include, &exclude)?
+ } else {
+ discover::from_paths(&options.paths, &base, &exclude)?
+ };
+ let files: Vec = files.iter().map(|path| display_path(path, cwd)).collect();
+
+ let mut linter = Linter::new()?;
+ let mut findings = Vec::new();
+ for path in &files {
+ let text = std::fs::read_to_string(path).map_err(|source| CheckError::Read {
+ path: path.clone(),
+ source,
+ })?;
+ findings.extend(linter.check(path, &text, &config, options.strict)?);
+ }
+
+ if !options.no_format {
+ findings.extend(format::check(&options.compact_bin, &files)?);
+ }
+
+ findings.sort_by(|left, right| left.sort_key().cmp(&right.sort_key()));
+ Ok(Outcome {
+ findings,
+ files_checked: files.len(),
+ })
+}
+
+/// An explicit `--config` is a shareable preset, so glob discovery stays anchored at
+/// `cwd`; a config found by walking upward anchors discovery at its own directory.
+fn resolve_config(
+ options: &Options,
+ cwd: &Path,
+) -> Result<(Config, Option, PathBuf), ConfigError> {
+ if let Some(path) = options.config_path.as_deref() {
+ return Ok((Config::load(path)?, Some(path.to_owned()), cwd.to_owned()));
+ }
+
+ match discover_config(cwd) {
+ Some(path) => {
+ let base = path.parent().unwrap_or(cwd).to_owned();
+ Ok((Config::load(&path)?, Some(path), base))
+ }
+ None => Ok((Config::default(), None, cwd.to_owned())),
+ }
+}
+
+/// Paths under the working directory print relative to it; anything else prints as is.
+fn display_path(path: &Path, cwd: &Path) -> PathBuf {
+ path.strip_prefix(cwd).unwrap_or(path).to_owned()
+}
diff --git a/crates/compact-lint/src/config.rs b/crates/compact-lint/src/config.rs
new file mode 100644
index 0000000..0d7ae2b
--- /dev/null
+++ b/crates/compact-lint/src/config.rs
@@ -0,0 +1,305 @@
+//! `compact-lint.toml`: discovery, parsing, and the built-in defaults.
+
+use std::path::{Path, PathBuf};
+
+use globset::{Glob, GlobSet, GlobSetBuilder};
+use serde::Deserialize;
+use thiserror::Error;
+
+use crate::doc::Tag;
+use crate::model::DeclKind;
+
+/// The config file name searched for upward from the current directory.
+pub const CONFIG_FILE_NAME: &str = "compact-lint.toml";
+
+/// The only schema version this build understands.
+pub const SUPPORTED_VERSION: u32 = 1;
+
+#[derive(Debug, Error)]
+pub enum ConfigError {
+ #[error("cannot read config {path}")]
+ Read {
+ path: PathBuf,
+ #[source]
+ source: std::io::Error,
+ },
+ #[error("cannot parse config {path}")]
+ Parse {
+ path: PathBuf,
+ #[source]
+ source: toml::de::Error,
+ },
+ #[error(
+ "config {path} is version {found}, but this build understands version {SUPPORTED_VERSION}"
+ )]
+ Version { path: PathBuf, found: u32 },
+ #[error(
+ "config {path} lists tag {tag:?}; tags are spelled with their `@`, like `@description`"
+ )]
+ Tag { path: PathBuf, tag: String },
+ #[error("config {path} has an invalid glob {pattern:?}")]
+ Glob {
+ path: PathBuf,
+ pattern: String,
+ #[source]
+ source: globset::Error,
+ },
+}
+
+/// How much of a kind needs documenting.
+#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
+#[serde(rename_all = "lowercase")]
+pub enum DocsPolicy {
+ /// Every declaration of the kind.
+ All,
+ /// Only declarations carrying the `export` token.
+ #[default]
+ Exported,
+ /// The kind is not checked for docs.
+ None,
+}
+
+/// Per-kind requirements.
+#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq)]
+#[serde(deny_unknown_fields, default)]
+pub struct KindConfig {
+ pub docs: DocsPolicy,
+ pub tags: Vec,
+}
+
+/// One table per checked declaration kind.
+#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq)]
+#[serde(deny_unknown_fields, default)]
+pub struct KindsConfig {
+ pub module: KindConfig,
+ pub circuit: KindConfig,
+ pub ledger: KindConfig,
+ pub witness: KindConfig,
+ pub constructor: KindConfig,
+ #[serde(rename = "struct")]
+ pub struct_: KindConfig,
+ #[serde(rename = "enum")]
+ pub enum_: KindConfig,
+ pub contract: KindConfig,
+ #[serde(rename = "type")]
+ pub type_: KindConfig,
+}
+
+impl KindsConfig {
+ #[must_use]
+ pub fn get(&self, kind: DeclKind) -> &KindConfig {
+ match kind {
+ DeclKind::Module => &self.module,
+ DeclKind::Circuit => &self.circuit,
+ DeclKind::Ledger => &self.ledger,
+ DeclKind::Witness => &self.witness,
+ DeclKind::Constructor => &self.constructor,
+ DeclKind::Struct => &self.struct_,
+ DeclKind::Enum => &self.enum_,
+ DeclKind::Contract => &self.contract,
+ DeclKind::Type => &self.type_,
+ }
+ }
+}
+
+/// The circuit constraints annotation.
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+#[serde(deny_unknown_fields, default)]
+pub struct ConstraintsConfig {
+ pub tag: Tag,
+}
+
+impl Default for ConstraintsConfig {
+ fn default() -> Self {
+ Self {
+ tag: Tag::new("@constraints"),
+ }
+ }
+}
+
+/// Tags no doc comment may carry.
+#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq)]
+#[serde(deny_unknown_fields, default)]
+pub struct TagsConfig {
+ pub forbid: Vec,
+}
+
+/// A parsed `compact-lint.toml`, or the built-in defaults when no file was found.
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+#[serde(deny_unknown_fields, default)]
+pub struct Config {
+ pub version: u32,
+ pub include: Vec,
+ pub exclude: Vec,
+ pub constraints: ConstraintsConfig,
+ pub tags: TagsConfig,
+ pub kinds: KindsConfig,
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ Self {
+ version: SUPPORTED_VERSION,
+ include: vec!["**/*.compact".to_owned()],
+ exclude: Vec::new(),
+ constraints: ConstraintsConfig::default(),
+ tags: TagsConfig::default(),
+ kinds: KindsConfig::default(),
+ }
+ }
+}
+
+impl Config {
+ /// Parses a config file and rejects a version this build does not implement.
+ /// # Errors
+ /// Returns an error when the file is unreadable, malformed, or a future version.
+ pub fn load(path: &Path) -> Result {
+ let text = std::fs::read_to_string(path).map_err(|source| ConfigError::Read {
+ path: path.to_owned(),
+ source,
+ })?;
+
+ let config: Self = toml::from_str(&text).map_err(|source| ConfigError::Parse {
+ path: path.to_owned(),
+ source,
+ })?;
+
+ if config.version != SUPPORTED_VERSION {
+ return Err(ConfigError::Version {
+ path: path.to_owned(),
+ found: config.version,
+ });
+ }
+
+ if let Some(tag) = config.tags().find(|tag| !tag.is_well_formed()) {
+ return Err(ConfigError::Tag {
+ path: path.to_owned(),
+ tag: tag.to_string(),
+ });
+ }
+
+ Ok(config)
+ }
+
+ /// Every tag the config mentions: required per kind, forbidden, and the constraints tag.
+ fn tags(&self) -> impl Iterator- {
+ let per_kind = [
+ &self.kinds.module,
+ &self.kinds.circuit,
+ &self.kinds.ledger,
+ &self.kinds.witness,
+ &self.kinds.constructor,
+ &self.kinds.struct_,
+ &self.kinds.enum_,
+ &self.kinds.contract,
+ &self.kinds.type_,
+ ]
+ .into_iter()
+ .flat_map(|kind| kind.tags.iter());
+
+ per_kind
+ .chain(self.tags.forbid.iter())
+ .chain(std::iter::once(&self.constraints.tag))
+ }
+
+ /// Compiles the `include` globs.
+ /// # Errors
+ /// Returns an error when a pattern is not a valid glob.
+ pub fn include_set(&self, path: &Path) -> Result {
+ glob_set(&self.include, path)
+ }
+
+ /// Compiles the `exclude` globs; an empty set matches nothing.
+ /// # Errors
+ /// Returns an error when a pattern is not a valid glob.
+ pub fn exclude_set(&self, path: &Path) -> Result {
+ glob_set(&self.exclude, path)
+ }
+}
+
+fn glob_set(patterns: &[String], path: &Path) -> Result {
+ let mut builder = GlobSetBuilder::new();
+ for pattern in patterns {
+ let glob = Glob::new(pattern).map_err(|source| ConfigError::Glob {
+ path: path.to_owned(),
+ pattern: pattern.clone(),
+ source,
+ })?;
+ builder.add(glob);
+ }
+ builder.build().map_err(|source| ConfigError::Glob {
+ path: path.to_owned(),
+ pattern: String::new(),
+ source,
+ })
+}
+
+/// Walks up from `start` looking for `compact-lint.toml`.
+#[must_use]
+pub fn discover(start: &Path) -> Option {
+ for directory in start.ancestors() {
+ let candidate = directory.join(CONFIG_FILE_NAME);
+ if candidate.is_file() {
+ return Some(candidate);
+ }
+ }
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{Config, DocsPolicy, SUPPORTED_VERSION};
+ use crate::doc::Tag;
+ use crate::model::DeclKind;
+
+ #[test]
+ fn defaults_require_docs_on_exported_declarations_only() {
+ let config = Config::default();
+
+ assert_eq!(config.version, SUPPORTED_VERSION);
+ assert_eq!(config.constraints.tag, Tag::new("@constraints"));
+ for kind in [DeclKind::Module, DeclKind::Circuit, DeclKind::Type] {
+ assert_eq!(config.kinds.get(kind).docs, DocsPolicy::Exported);
+ assert!(config.kinds.get(kind).tags.is_empty());
+ }
+ }
+
+ #[test]
+ fn omitted_tables_fall_back_to_defaults() {
+ let config: Config = toml::from_str("version = 1\n[kinds.witness]\ndocs = \"all\"\n")
+ .expect("the snippet is valid config");
+
+ assert_eq!(config.kinds.witness.docs, DocsPolicy::All);
+ assert_eq!(config.kinds.circuit.docs, DocsPolicy::Exported);
+ assert_eq!(config.include, ["**/*.compact"]);
+ }
+
+ #[test]
+ fn an_unknown_key_is_rejected() {
+ let error = toml::from_str::("verison = 1\n").expect_err("the key is misspelled");
+
+ assert!(error.to_string().contains("verison"), "{error}");
+ }
+
+ #[test]
+ fn a_tag_without_its_at_sign_is_rejected() {
+ let dir = std::env::temp_dir().join(format!("compact-lint-config-{}", std::process::id()));
+ std::fs::create_dir_all(&dir).expect("the temp dir is writable");
+ let path = dir.join("compact-lint.toml");
+ std::fs::write(&path, "[kinds.circuit]\ntags = [\"description\"]\n")
+ .expect("the config is writable");
+
+ let error = Config::load(&path).expect_err("the tag lacks its @");
+
+ assert!(error.to_string().contains("\"description\""), "{error}");
+ std::fs::remove_dir_all(&dir).expect("the temp dir is removable");
+ }
+
+ #[test]
+ fn an_unknown_kind_is_rejected() {
+ let error =
+ toml::from_str::("[kinds.circiut]\n").expect_err("the kind is misspelled");
+
+ assert!(error.to_string().contains("circiut"), "{error}");
+ }
+}
diff --git a/crates/compact-lint/src/discover.rs b/crates/compact-lint/src/discover.rs
new file mode 100644
index 0000000..f647c9f
--- /dev/null
+++ b/crates/compact-lint/src/discover.rs
@@ -0,0 +1,114 @@
+//! Turning CLI paths and config globs into the list of files to check.
+
+use std::path::{Path, PathBuf};
+
+use globset::GlobSet;
+use thiserror::Error;
+use walkdir::WalkDir;
+
+/// Directories that never hold first-party Compact sources.
+const SKIPPED_DIRS: [&str; 5] = ["node_modules", "target", "dist", "build", ".git"];
+
+const COMPACT_EXTENSION: &str = "compact";
+
+#[derive(Debug, Error)]
+pub enum DiscoverError {
+ #[error("cannot walk {path}")]
+ Walk {
+ path: PathBuf,
+ #[source]
+ source: walkdir::Error,
+ },
+ #[error("path {0} does not exist")]
+ Missing(PathBuf),
+}
+
+/// Collects the `.compact` files under the given paths.
+///
+/// A path named on the command line is always checked, even when `exclude` matches
+/// it; files found by walking a directory are filtered by `exclude`.
+/// # Errors
+/// Returns an error when a path does not exist or a directory cannot be walked.
+pub fn from_paths(
+ paths: &[PathBuf],
+ base: &Path,
+ exclude: &GlobSet,
+) -> Result, DiscoverError> {
+ let mut files = Vec::new();
+
+ for path in paths {
+ if path.is_file() {
+ files.push(path.clone());
+ } else if path.is_dir() {
+ walk(path, base, exclude, None, &mut files)?;
+ } else {
+ return Err(DiscoverError::Missing(path.clone()));
+ }
+ }
+
+ Ok(sorted(files))
+}
+
+/// Collects the `.compact` files under `base` that `include` matches and `exclude` does not.
+/// # Errors
+/// Returns an error when `base` cannot be walked.
+pub fn from_globs(
+ base: &Path,
+ include: &GlobSet,
+ exclude: &GlobSet,
+) -> Result, DiscoverError> {
+ let mut files = Vec::new();
+ walk(base, base, exclude, Some(include), &mut files)?;
+ Ok(sorted(files))
+}
+
+fn walk(
+ root: &Path,
+ base: &Path,
+ exclude: &GlobSet,
+ include: Option<&GlobSet>,
+ out: &mut Vec,
+) -> Result<(), DiscoverError> {
+ let walker = WalkDir::new(root)
+ .follow_links(false)
+ .into_iter()
+ .filter_entry(|entry| entry.depth() == 0 || !is_skipped_dir(entry.file_name()));
+
+ for entry in walker {
+ let entry = entry.map_err(|source| DiscoverError::Walk {
+ path: root.to_owned(),
+ source,
+ })?;
+
+ if !entry.file_type().is_file() {
+ continue;
+ }
+
+ let path = entry.path();
+ if path
+ .extension()
+ .is_none_or(|extension| extension != COMPACT_EXTENSION)
+ {
+ continue;
+ }
+
+ let relative = path.strip_prefix(base).unwrap_or(path);
+ if include.is_some_and(|set| !set.is_match(relative)) || exclude.is_match(relative) {
+ continue;
+ }
+
+ out.push(path.to_owned());
+ }
+
+ Ok(())
+}
+
+fn is_skipped_dir(name: &std::ffi::OsStr) -> bool {
+ SKIPPED_DIRS.iter().any(|skipped| name == *skipped)
+}
+
+fn sorted(mut files: Vec) -> Vec {
+ files.sort();
+ files.dedup();
+ files
+}
diff --git a/crates/compact-lint/src/doc.rs b/crates/compact-lint/src/doc.rs
new file mode 100644
index 0000000..685007f
--- /dev/null
+++ b/crates/compact-lint/src/doc.rs
@@ -0,0 +1,211 @@
+//! Parsing of `/** … */` doc comments into an ordered tag list.
+
+use std::fmt;
+
+use serde::Deserialize;
+
+/// A doc-comment tag name, stored with its leading `@` (`@description`).
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[serde(transparent)]
+pub struct Tag(String);
+
+impl Tag {
+ pub fn new(name: impl Into) -> Self {
+ Self(name.into())
+ }
+
+ #[must_use]
+ pub fn as_str(&self) -> &str {
+ &self.0
+ }
+
+ /// `@` followed by at least one ASCII letter, the same shape the parser recognises.
+ #[must_use]
+ pub fn is_well_formed(&self) -> bool {
+ self.0.strip_prefix('@').is_some_and(|name| {
+ !name.is_empty() && name.bytes().all(|byte| byte.is_ascii_alphabetic())
+ })
+ }
+}
+
+impl fmt::Display for Tag {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str(&self.0)
+ }
+}
+
+/// One tag occurrence: its name, its value, and where it sits inside the comment.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct TagOccurrence {
+ pub tag: Tag,
+ pub value: String,
+ /// 0-based line index inside the doc comment, counted from the `/**` line.
+ pub line_offset: usize,
+ /// 0-based byte offset of the `@` in its line; on the first line, counted after `/**`.
+ pub column_offset: usize,
+}
+
+/// A parsed doc comment. Repeated tags (`@param`) are kept in source order.
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct DocComment {
+ tags: Vec,
+}
+
+impl DocComment {
+ /// Parses the raw `/** … */` text, stripping the delimiters and the ` * ` gutter.
+ #[must_use]
+ pub fn parse(raw: &str) -> Self {
+ let opened = raw.strip_prefix("/**").unwrap_or(raw);
+ let inner = opened.strip_suffix("*/").unwrap_or(opened);
+
+ let mut tags: Vec = Vec::new();
+ let mut open: Option = None;
+
+ for (line_offset, line) in inner.lines().enumerate() {
+ let content = strip_gutter(line).trim();
+
+ if content.is_empty() {
+ open = None;
+ continue;
+ }
+
+ if let Some((tag, value)) = split_tag(content) {
+ tags.push(TagOccurrence {
+ tag,
+ value: value.to_owned(),
+ line_offset,
+ column_offset: line.find('@').unwrap_or(0),
+ });
+ open = Some(tags.len() - 1);
+ continue;
+ }
+
+ // A non-blank, non-tag line continues the tag above it.
+ if let Some(index) = open
+ && let Some(occurrence) = tags.get_mut(index)
+ {
+ if !occurrence.value.is_empty() {
+ occurrence.value.push('\n');
+ }
+ occurrence.value.push_str(content);
+ }
+ }
+
+ Self { tags }
+ }
+
+ /// Every tag occurrence, in source order.
+ #[must_use]
+ pub fn tags(&self) -> &[TagOccurrence] {
+ &self.tags
+ }
+
+ #[must_use]
+ pub fn has(&self, tag: &Tag) -> bool {
+ self.tags.iter().any(|occurrence| &occurrence.tag == tag)
+ }
+
+ /// The first occurrence of `tag`, which is the one every rule reads.
+ #[must_use]
+ pub fn first(&self, tag: &Tag) -> Option<&TagOccurrence> {
+ self.tags.iter().find(|occurrence| &occurrence.tag == tag)
+ }
+
+ /// The value of the first occurrence of `tag`.
+ #[must_use]
+ pub fn value(&self, tag: &Tag) -> Option<&str> {
+ self.first(tag).map(|occurrence| occurrence.value.as_str())
+ }
+}
+
+/// Removes the leading whitespace, one `*`, and one following space from a comment line.
+fn strip_gutter(line: &str) -> &str {
+ let trimmed = line.trim_start();
+ match trimmed.strip_prefix('*') {
+ Some(rest) => rest.strip_prefix(' ').unwrap_or(rest),
+ None => trimmed,
+ }
+}
+
+/// A tag line starts with `@` followed by at least one ASCII letter.
+fn split_tag(content: &str) -> Option<(Tag, &str)> {
+ let rest = content.strip_prefix('@')?;
+ let name_len = rest
+ .find(|character: char| !character.is_ascii_alphabetic())
+ .unwrap_or(rest.len());
+ if name_len == 0 {
+ return None;
+ }
+
+ let (name, value) = rest.split_at(name_len);
+ Some((Tag::new(format!("@{name}")), value.trim()))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{DocComment, Tag};
+
+ #[test]
+ fn gutter_and_delimiters_are_stripped() {
+ let doc = DocComment::parse("/**\n * @description Adds two numbers.\n */");
+
+ assert_eq!(doc.tags().len(), 1);
+ assert_eq!(doc.tags()[0].tag, Tag::new("@description"));
+ assert_eq!(doc.tags()[0].value, "Adds two numbers.");
+ assert_eq!(doc.tags()[0].line_offset, 1);
+ assert_eq!(doc.tags()[0].column_offset, 3);
+ }
+
+ #[test]
+ fn a_blank_line_ends_a_value() {
+ let doc = DocComment::parse("/**\n * @notice first\n *\n * loose prose\n */");
+
+ assert_eq!(doc.value(&Tag::new("@notice")), Some("first"));
+ }
+
+ #[test]
+ fn a_non_tag_line_continues_the_tag_above_it() {
+ let doc = DocComment::parse("/**\n * @notice first\n * second\n */");
+
+ assert_eq!(doc.value(&Tag::new("@notice")), Some("first\nsecond"));
+ }
+
+ #[test]
+ fn repeated_tags_keep_source_order() {
+ let doc = DocComment::parse(
+ "/**\n * @param {Uint<8>} a - left\n * @param {Uint<8>} b - right\n */",
+ );
+
+ let params: Vec<&str> = doc
+ .tags()
+ .iter()
+ .filter(|occurrence| occurrence.tag == Tag::new("@param"))
+ .map(|occurrence| occurrence.value.as_str())
+ .collect();
+ assert_eq!(params, ["{Uint<8>} a - left", "{Uint<8>} b - right"]);
+ }
+
+ #[test]
+ fn return_and_returns_are_distinct_tags() {
+ let doc = DocComment::parse("/**\n * @returns The sum.\n */");
+
+ assert!(doc.has(&Tag::new("@returns")));
+ assert!(!doc.has(&Tag::new("@return")));
+ }
+
+ #[test]
+ fn an_at_sign_without_letters_is_not_a_tag() {
+ let doc = DocComment::parse("/**\n * @1234 not a tag\n * mail@example.com\n */");
+
+ assert!(doc.tags().is_empty());
+ }
+
+ #[test]
+ fn a_single_line_comment_parses() {
+ let doc = DocComment::parse("/** @description One liner. */");
+
+ assert_eq!(doc.value(&Tag::new("@description")), Some("One liner."));
+ assert_eq!(doc.tags()[0].line_offset, 0);
+ assert_eq!(doc.tags()[0].column_offset, 1);
+ }
+}
diff --git a/crates/compact-lint/src/format.rs b/crates/compact-lint/src/format.rs
new file mode 100644
index 0000000..d3a0c46
--- /dev/null
+++ b/crates/compact-lint/src/format.rs
@@ -0,0 +1,156 @@
+//! The optional `compact format --check` pass.
+
+use std::ffi::OsStr;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+use thiserror::Error;
+
+use crate::report::{Finding, Position, RuleId};
+
+/// Environment override for the `compact` binary; the `--compact-bin` flag wins over it.
+pub const COMPACT_BIN_ENV: &str = "COMPACT_LINT_COMPACT_BIN";
+
+/// The default binary name, resolved through `PATH`.
+pub const DEFAULT_COMPACT_BIN: &str = "compact";
+
+pub const MESSAGE: &str = "not formatted; run `compact format`";
+
+#[derive(Debug, Error)]
+pub enum FormatError {
+ #[error("cannot run `{binary}`; pass --no-format to skip the format check")]
+ BinaryMissing { binary: String },
+ #[error("cannot run `{binary}`")]
+ Spawn {
+ binary: String,
+ #[source]
+ source: std::io::Error,
+ },
+}
+
+/// Runs `compact format --check` once over every file and maps its report to findings.
+///
+/// The formatter writes one `:` line per unformatted file to stderr, followed by
+/// an indented diff. An output that holds no such line yields one finding on the first
+/// checked file carrying the formatter's own first line.
+/// # Errors
+/// Returns an error when the `compact` binary is missing or cannot be spawned.
+pub fn check(binary: &OsStr, files: &[PathBuf]) -> Result, FormatError> {
+ if files.is_empty() {
+ return Ok(Vec::new());
+ }
+
+ let output = Command::new(binary)
+ .arg("format")
+ .arg("--check")
+ .args(files)
+ .output()
+ .map_err(|source| {
+ let binary = binary.to_string_lossy().into_owned();
+ if source.kind() == std::io::ErrorKind::NotFound {
+ FormatError::BinaryMissing { binary }
+ } else {
+ FormatError::Spawn { binary, source }
+ }
+ })?;
+
+ if output.status.success() {
+ return Ok(Vec::new());
+ }
+
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ let stdout = String::from_utf8_lossy(&output.stdout);
+ let reported: Vec<&str> = reported_paths(&stderr)
+ .chain(reported_paths(&stdout))
+ .collect();
+
+ if reported.is_empty() {
+ let raw = first_line(&stderr)
+ .or_else(|| first_line(&stdout))
+ .unwrap_or("compact format --check failed");
+ return Ok(vec![Finding::new(
+ files[0].clone(),
+ Position::file_start(),
+ RuleId::FORMAT,
+ raw.to_owned(),
+ )]);
+ }
+
+ Ok(reported
+ .into_iter()
+ .map(|path| {
+ Finding::new(
+ resolve(path, files),
+ Position::file_start(),
+ RuleId::FORMAT,
+ MESSAGE.to_owned(),
+ )
+ })
+ .collect())
+}
+
+/// Unformatted files are announced as a flush-left `:` line.
+fn reported_paths(output: &str) -> impl Iterator
- {
+ output.lines().filter_map(|line| {
+ let path = line.strip_suffix(':')?;
+ (!path.is_empty() && !path.starts_with(char::is_whitespace) && path.ends_with(".compact"))
+ .then_some(path)
+ })
+}
+
+/// Prefers the path spelling the caller passed in, so output stays consistent.
+/// Matching is by whole path components, so `Token.compact` never claims `NativeToken.compact`.
+fn resolve(reported: &str, files: &[PathBuf]) -> PathBuf {
+ let reported = Path::new(reported);
+ files
+ .iter()
+ .find(|file| reported.ends_with(file) || file.ends_with(reported))
+ .cloned()
+ .unwrap_or_else(|| reported.to_owned())
+}
+
+fn first_line(output: &str) -> Option<&str> {
+ output.lines().map(str::trim).find(|line| !line.is_empty())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{reported_paths, resolve};
+ use std::path::PathBuf;
+
+ const SAMPLE: &str = "/tmp/bad.compact:\n 18 | export ledger round: Counter;\n- 21 | - round.increment(1);\n+ 21 | + round.increment(1);\n\nError: formatting failed\n";
+
+ #[test]
+ fn flush_left_path_lines_become_findings() {
+ let paths: Vec<&str> = reported_paths(SAMPLE).collect();
+
+ assert_eq!(paths, ["/tmp/bad.compact"]);
+ }
+
+ #[test]
+ fn indented_diff_lines_are_not_paths() {
+ let paths: Vec<&str> = reported_paths(" a.compact:\n\ttrailing:\n").collect();
+
+ assert!(paths.is_empty());
+ }
+
+ #[test]
+ fn an_absolute_report_maps_back_to_the_relative_path_passed_in() {
+ let files = [PathBuf::from("contracts/src/Token.compact")];
+
+ assert_eq!(
+ resolve("/work/contracts/src/Token.compact", &files),
+ PathBuf::from("contracts/src/Token.compact")
+ );
+ }
+
+ #[test]
+ fn a_shorter_file_name_with_the_same_suffix_is_not_a_match() {
+ let files = [PathBuf::from("Token.compact")];
+
+ assert_eq!(
+ resolve("/work/NativeToken.compact", &files),
+ PathBuf::from("/work/NativeToken.compact")
+ );
+ }
+}
diff --git a/crates/compact-lint/src/lib.rs b/crates/compact-lint/src/lib.rs
new file mode 100644
index 0000000..fdaf6e9
--- /dev/null
+++ b/crates/compact-lint/src/lib.rs
@@ -0,0 +1,16 @@
+//! Doc-comment linter for Compact sources.
+//!
+//! The crate parses `.compact` files with the bundled tree-sitter grammar, matches each
+//! declaration against the per-kind doc-comment template in `compact-lint.toml`, and
+//! reports one [`report::Finding`] per violation.
+
+#![forbid(unsafe_code)]
+
+pub mod check;
+pub mod config;
+pub mod discover;
+pub mod doc;
+pub mod format;
+pub mod model;
+pub mod report;
+pub mod rules;
diff --git a/crates/compact-lint/src/main.rs b/crates/compact-lint/src/main.rs
new file mode 100644
index 0000000..dc4d364
--- /dev/null
+++ b/crates/compact-lint/src/main.rs
@@ -0,0 +1,114 @@
+//! `compact-lint` command line entry point.
+
+#![forbid(unsafe_code)]
+
+use std::ffi::OsString;
+use std::io::Write;
+use std::path::PathBuf;
+use std::process::ExitCode;
+
+use anyhow::{Context, Result};
+use clap::{Parser, Subcommand};
+
+use compact_lint::check::{self, Options, Outcome};
+use compact_lint::format::{COMPACT_BIN_ENV, DEFAULT_COMPACT_BIN};
+
+/// Exit code for a run that produced findings.
+const EXIT_FINDINGS: u8 = 1;
+
+/// Exit code for a usage, config, IO or parser error.
+const EXIT_ERROR: u8 = 2;
+
+#[derive(Debug, Parser)]
+#[command(
+ name = "compact-lint",
+ version,
+ about = "Doc-comment linter for Compact sources"
+)]
+struct Cli {
+ #[command(subcommand)]
+ command: Command,
+}
+
+#[derive(Debug, Subcommand)]
+enum Command {
+ /// Check doc comments against the per-kind templates in compact-lint.toml.
+ Check(CheckArgs),
+}
+
+#[derive(Debug, clap::Args)]
+struct CheckArgs {
+ /// Files or directories to check; defaults to the config's include globs.
+ paths: Vec,
+
+ /// Config file to use instead of searching upward for compact-lint.toml.
+ #[arg(long, value_name = "FILE")]
+ config: Option,
+
+ /// Report `k=?` / `rows=?` placeholders in the constraints tag.
+ #[arg(long)]
+ strict: bool,
+
+ /// Skip the `compact format --check` pass.
+ #[arg(long)]
+ no_format: bool,
+
+ /// Path to the `compact` binary.
+ #[arg(long, value_name = "PATH", env = COMPACT_BIN_ENV)]
+ compact_bin: Option,
+}
+
+fn main() -> ExitCode {
+ match run() {
+ Ok(code) => code,
+ Err(error) => {
+ report_error(&error);
+ ExitCode::from(EXIT_ERROR)
+ }
+ }
+}
+
+fn run() -> Result {
+ let cli = Cli::parse();
+ let Command::Check(args) = cli.command;
+
+ let cwd = std::env::current_dir().context("reading the current directory")?;
+ let options = Options {
+ paths: args.paths,
+ config_path: args.config,
+ strict: args.strict,
+ no_format: args.no_format,
+ compact_bin: args
+ .compact_bin
+ .unwrap_or_else(|| OsString::from(DEFAULT_COMPACT_BIN)),
+ };
+
+ let outcome = check::run(&options, &cwd).context("running the check")?;
+ emit(&outcome).context("writing the report")?;
+
+ Ok(if outcome.findings.is_empty() {
+ ExitCode::SUCCESS
+ } else {
+ ExitCode::from(EXIT_FINDINGS)
+ })
+}
+
+fn emit(outcome: &Outcome) -> std::io::Result<()> {
+ let stdout = std::io::stdout();
+ let mut out = stdout.lock();
+ for finding in &outcome.findings {
+ writeln!(out, "{finding}")?;
+ }
+ out.flush()?;
+
+ eprintln!("{}", outcome.summary());
+ Ok(())
+}
+
+/// Prints the error and every source under it, one cause per line.
+fn report_error(error: &anyhow::Error) {
+ eprintln!("compact-lint: {error}");
+ for cause in error.chain().skip(1) {
+ eprintln!(" caused by: {cause}");
+ }
+}
diff --git a/crates/compact-lint/src/model.rs b/crates/compact-lint/src/model.rs
new file mode 100644
index 0000000..968eee8
--- /dev/null
+++ b/crates/compact-lint/src/model.rs
@@ -0,0 +1,177 @@
+//! Declarations the linter checks, lifted out of the tree-sitter tree.
+
+use std::fmt;
+
+use tree_sitter::Node;
+
+use crate::doc::{DocComment, TagOccurrence};
+use crate::report::Position;
+
+/// The declaration kinds the linter has rules for.
+///
+/// `pragma`, `include`, `import`, `export { … }` and `contract implements` are
+/// never checked, so they have no variant here.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub enum DeclKind {
+ Module,
+ Circuit,
+ Ledger,
+ Witness,
+ Constructor,
+ Struct,
+ Enum,
+ Contract,
+ Type,
+}
+
+impl DeclKind {
+ /// Maps a tree-sitter node kind, returning `None` for unchecked declarations.
+ #[must_use]
+ pub fn from_node_kind(node_kind: &str) -> Option {
+ match node_kind {
+ "module_declaration" => Some(Self::Module),
+ "circuit_declaration" => Some(Self::Circuit),
+ "ledger_declaration" => Some(Self::Ledger),
+ "witness_declaration" => Some(Self::Witness),
+ "constructor_declaration" => Some(Self::Constructor),
+ "struct_declaration" => Some(Self::Struct),
+ "enum_declaration" => Some(Self::Enum),
+ "contract_declaration" => Some(Self::Contract),
+ "type_declaration" => Some(Self::Type),
+ _ => None,
+ }
+ }
+
+ /// The name used in config keys and messages.
+ #[must_use]
+ pub const fn as_str(self) -> &'static str {
+ match self {
+ Self::Module => "module",
+ Self::Circuit => "circuit",
+ Self::Ledger => "ledger",
+ Self::Witness => "witness",
+ Self::Constructor => "constructor",
+ Self::Struct => "struct",
+ Self::Enum => "enum",
+ Self::Contract => "contract",
+ Self::Type => "type",
+ }
+ }
+}
+
+impl fmt::Display for DeclKind {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str(self.as_str())
+ }
+}
+
+/// One declaration with the facts the rules need.
+#[derive(Clone, Debug)]
+pub struct Declaration {
+ pub kind: DeclKind,
+ /// The `name` field; `constructor` has none.
+ pub name: Option,
+ /// The declaration carries the `export` token itself; nesting never grants it.
+ pub exported: bool,
+ pub pure: bool,
+ pub position: Position,
+ /// The doc comment immediately preceding the declaration, if one attaches.
+ pub doc: Option,
+}
+
+/// A doc comment bound to a declaration, with the source line it starts on.
+#[derive(Clone, Debug)]
+pub struct AttachedDoc {
+ pub comment: DocComment,
+ /// 0-based row of the `/**` line, for turning a tag's offset into a position.
+ pub start_row: usize,
+ pub start_column: usize,
+}
+
+impl AttachedDoc {
+ /// The position of a tag's `@`; a first-line offset is counted after the `/**`.
+ #[must_use]
+ pub const fn tag_position(&self, occurrence: &TagOccurrence) -> Position {
+ let column = if occurrence.line_offset == 0 {
+ self.start_column + "/**".len() + occurrence.column_offset
+ } else {
+ occurrence.column_offset
+ };
+ Position::from_zero_based(self.start_row + occurrence.line_offset, column)
+ }
+}
+
+/// Collects every checked declaration, descending into module bodies.
+#[must_use]
+pub fn declarations(root: Node<'_>, source: &str) -> Vec {
+ let mut found = Vec::new();
+ collect(root, source, &mut found);
+ found
+}
+
+fn collect(parent: Node<'_>, source: &str, out: &mut Vec) {
+ let mut cursor = parent.walk();
+ for node in parent.named_children(&mut cursor) {
+ let Some(kind) = DeclKind::from_node_kind(node.kind()) else {
+ continue;
+ };
+
+ out.push(declaration(kind, node, source));
+
+ // Only modules nest; every other body is an opaque token run.
+ if kind == DeclKind::Module
+ && let Some(body) = node.child_by_field_name("body")
+ {
+ collect(body, source, out);
+ }
+ }
+}
+
+fn declaration(kind: DeclKind, node: Node<'_>, source: &str) -> Declaration {
+ let mut cursor = node.walk();
+ let mut exported = false;
+ let mut pure = false;
+ for child in node.children(&mut cursor) {
+ match child.kind() {
+ "export" => exported = true,
+ "pure" => pure = true,
+ _ => {}
+ }
+ }
+
+ let name = node
+ .child_by_field_name("name")
+ .and_then(|name| text(name, source))
+ .map(str::to_owned);
+
+ let start = node.start_position();
+
+ Declaration {
+ kind,
+ name,
+ exported,
+ pure,
+ position: Position::from_zero_based(start.row, start.column),
+ doc: attached_doc(node, source),
+ }
+}
+
+/// A doc comment attaches only as the immediately preceding named sibling; a line
+/// or block comment in between breaks the attachment.
+fn attached_doc(node: Node<'_>, source: &str) -> Option {
+ let previous = node.prev_named_sibling()?;
+ if previous.kind() != "doc_comment" {
+ return None;
+ }
+
+ let start = previous.start_position();
+ Some(AttachedDoc {
+ comment: DocComment::parse(text(previous, source)?),
+ start_row: start.row,
+ start_column: start.column,
+ })
+}
+
+fn text<'a>(node: Node<'_>, source: &'a str) -> Option<&'a str> {
+ source.get(node.start_byte()..node.end_byte())
+}
diff --git a/crates/compact-lint/src/report.rs b/crates/compact-lint/src/report.rs
new file mode 100644
index 0000000..b2d9f17
--- /dev/null
+++ b/crates/compact-lint/src/report.rs
@@ -0,0 +1,142 @@
+//! Findings and the one-line `path:line:col: rule: message` rendering.
+
+use std::fmt;
+use std::path::{Path, PathBuf};
+
+/// A lint rule's stable identifier, as printed in the output and matched by CI greps.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct RuleId(&'static str);
+
+impl RuleId {
+ pub const MISSING_DOC: Self = Self("missing-doc");
+ pub const MISSING_TAG: Self = Self("missing-tag");
+ pub const FORBIDDEN_TAG: Self = Self("forbidden-tag");
+ pub const MODULE_NAME: Self = Self("module-name");
+ pub const MISSING_CONSTRAINTS: Self = Self("missing-constraints");
+ pub const CONSTRAINTS_FORMAT: Self = Self("constraints-format");
+ pub const CONSTRAINTS_PLACEHOLDER: Self = Self("constraints-placeholder");
+ pub const PARSE: Self = Self("parse");
+ pub const FORMAT: Self = Self("format");
+
+ /// Every rule this build can emit, in the order the README lists them.
+ pub const ALL: [Self; 9] = [
+ Self::MISSING_DOC,
+ Self::MISSING_TAG,
+ Self::FORBIDDEN_TAG,
+ Self::MODULE_NAME,
+ Self::MISSING_CONSTRAINTS,
+ Self::CONSTRAINTS_FORMAT,
+ Self::CONSTRAINTS_PLACEHOLDER,
+ Self::PARSE,
+ Self::FORMAT,
+ ];
+
+ #[must_use]
+ pub const fn as_str(self) -> &'static str {
+ self.0
+ }
+}
+
+impl fmt::Display for RuleId {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str(self.0)
+ }
+}
+
+/// A 1-based source position; column 1 is the first byte of a line.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Position {
+ pub line: usize,
+ pub column: usize,
+}
+
+impl Position {
+ /// Converts a tree-sitter 0-based row/column pair.
+ #[must_use]
+ pub const fn from_zero_based(row: usize, column: usize) -> Self {
+ Self {
+ line: row + 1,
+ column: column + 1,
+ }
+ }
+
+ /// The start of a file, used by whole-file findings.
+ #[must_use]
+ pub const fn file_start() -> Self {
+ Self { line: 1, column: 1 }
+ }
+}
+
+/// One lint violation at one source position.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Finding {
+ pub path: PathBuf,
+ pub position: Position,
+ pub rule: RuleId,
+ pub message: String,
+}
+
+impl Finding {
+ pub fn new(
+ path: impl Into,
+ position: Position,
+ rule: RuleId,
+ message: String,
+ ) -> Self {
+ Self {
+ path: path.into(),
+ position,
+ rule,
+ message,
+ }
+ }
+
+ /// Sorts findings by file, then position, then rule, so output is stable across runs.
+ #[must_use]
+ pub fn sort_key(&self) -> (&Path, Position, RuleId) {
+ (self.path.as_path(), self.position, self.rule)
+ }
+}
+
+impl fmt::Display for Finding {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ formatter,
+ "{}:{}:{}: {}: {}",
+ self.path.display(),
+ self.position.line,
+ self.position.column,
+ self.rule,
+ self.message
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{Finding, Position, RuleId};
+
+ #[test]
+ fn finding_renders_as_path_line_col_rule_message() {
+ let finding = Finding::new(
+ "contracts/src/access/Ownable.compact",
+ Position::from_zero_based(41, 0),
+ RuleId::MISSING_DOC,
+ "circuit `assertOnlyOwner` has no doc comment".to_owned(),
+ );
+
+ assert_eq!(
+ finding.to_string(),
+ "contracts/src/access/Ownable.compact:42:1: missing-doc: circuit `assertOnlyOwner` has no doc comment"
+ );
+ }
+
+ #[test]
+ fn rule_ids_are_unique() {
+ let mut ids: Vec<&str> = RuleId::ALL.iter().map(|rule| rule.as_str()).collect();
+ ids.sort_unstable();
+ let count = ids.len();
+ ids.dedup();
+ assert_eq!(ids.len(), count);
+ }
+}
diff --git a/crates/compact-lint/src/rules.rs b/crates/compact-lint/src/rules.rs
new file mode 100644
index 0000000..9bc0bf7
--- /dev/null
+++ b/crates/compact-lint/src/rules.rs
@@ -0,0 +1,415 @@
+//! The doc-comment rules, run over one parsed file at a time.
+
+use std::path::{Path, PathBuf};
+
+use thiserror::Error;
+use tree_sitter::{Node, Parser};
+
+use crate::config::{Config, DocsPolicy};
+use crate::doc::Tag;
+use crate::model::{DeclKind, Declaration, declarations};
+use crate::report::{Finding, Position, RuleId};
+
+#[derive(Debug, Error)]
+pub enum LintError {
+ #[error("the bundled Compact grammar does not match the linked tree-sitter ABI")]
+ Language(#[source] tree_sitter::LanguageError),
+ #[error("tree-sitter returned no parse tree for {0}")]
+ NoTree(PathBuf),
+}
+
+/// A reusable parser plus the rules that run over its trees.
+pub struct Linter {
+ parser: Parser,
+}
+
+impl Linter {
+ /// # Errors
+ /// Returns an error when the bundled grammar does not match the linked ABI.
+ pub fn new() -> Result {
+ let mut parser = Parser::new();
+ parser
+ .set_language(&compact_tree_sitter::LANGUAGE.into())
+ .map_err(LintError::Language)?;
+ Ok(Self { parser })
+ }
+
+ /// Checks one file. `display` is the path printed in findings.
+ ///
+ /// A tree holding an `ERROR` or `MISSING` node yields a single `parse` finding and
+ /// no doc findings, because declarations around the defect are not trustworthy.
+ /// # Errors
+ /// Returns an error when tree-sitter produces no tree for `source`.
+ pub fn check(
+ &mut self,
+ display: &Path,
+ source: &str,
+ config: &Config,
+ strict: bool,
+ ) -> Result, LintError> {
+ let tree = self
+ .parser
+ .parse(source, None)
+ .ok_or_else(|| LintError::NoTree(display.to_owned()))?;
+ let root = tree.root_node();
+
+ if let Some(defect) = first_defect(root) {
+ return Ok(vec![defect.finding(display)]);
+ }
+
+ let mut findings = Vec::new();
+ for declaration in declarations(root, source) {
+ check_declaration(&declaration, display, config, strict, &mut findings);
+ }
+ findings.sort_by(|left, right| left.sort_key().cmp(&right.sort_key()));
+ Ok(findings)
+ }
+}
+
+struct Defect {
+ position: Position,
+ kind: String,
+ missing: bool,
+}
+
+impl Defect {
+ fn finding(&self, display: &Path) -> Finding {
+ let message = if self.missing {
+ format!("parse error: missing {}", self.kind)
+ } else {
+ format!("parse error: unexpected {}", self.kind)
+ };
+ Finding::new(display, self.position, RuleId::PARSE, message)
+ }
+}
+
+/// The first `ERROR` or `MISSING` node in document order.
+fn first_defect(node: Node<'_>) -> Option {
+ if node.is_error() || node.is_missing() {
+ let start = node.start_position();
+ return Some(Defect {
+ position: Position::from_zero_based(start.row, start.column),
+ kind: node.kind().to_owned(),
+ missing: node.is_missing(),
+ });
+ }
+
+ if !node.has_error() {
+ return None;
+ }
+
+ let mut cursor = node.walk();
+ node.children(&mut cursor).find_map(first_defect)
+}
+
+fn check_declaration(
+ declaration: &Declaration,
+ display: &Path,
+ config: &Config,
+ strict: bool,
+ findings: &mut Vec,
+) {
+ let kind_config = config.kinds.get(declaration.kind);
+ let requires_docs = match kind_config.docs {
+ DocsPolicy::All => true,
+ DocsPolicy::Exported => declaration.exported,
+ DocsPolicy::None => false,
+ };
+
+ let Some(attached) = declaration.doc.as_ref() else {
+ if requires_docs {
+ findings.push(Finding::new(
+ display,
+ declaration.position,
+ RuleId::MISSING_DOC,
+ format!("{} has no doc comment", subject(declaration)),
+ ));
+ }
+ return;
+ };
+
+ // A forbidden tag is a finding wherever it appears, documented or not.
+ for forbidden in &config.tags.forbid {
+ for occurrence in attached.comment.tags() {
+ if &occurrence.tag == forbidden {
+ findings.push(Finding::new(
+ display,
+ attached.tag_position(occurrence),
+ RuleId::FORBIDDEN_TAG,
+ format!("forbidden tag {forbidden}"),
+ ));
+ }
+ }
+ }
+
+ if declaration.kind == DeclKind::Module {
+ check_module_name(declaration, display, findings);
+ }
+
+ if !requires_docs {
+ return;
+ }
+
+ for required in &kind_config.tags {
+ if !attached.comment.has(required) {
+ findings.push(Finding::new(
+ display,
+ declaration.position,
+ RuleId::MISSING_TAG,
+ format!("{} doc comment has no {required}", subject(declaration)),
+ ));
+ }
+ }
+
+ check_constraints(declaration, display, config, strict, findings);
+}
+
+/// `@module ` must name the module it documents.
+fn check_module_name(declaration: &Declaration, display: &Path, findings: &mut Vec) {
+ let tag = Tag::new("@module");
+ let (Some(attached), Some(name)) = (declaration.doc.as_ref(), declaration.name.as_deref())
+ else {
+ return;
+ };
+ let Some(occurrence) = attached.comment.first(&tag) else {
+ return;
+ };
+
+ let documented = module_name(&occurrence.value);
+ if documented != name {
+ findings.push(Finding::new(
+ display,
+ attached.tag_position(occurrence),
+ RuleId::MODULE_NAME,
+ format!("@module names `{documented}`, but the module is `{name}`"),
+ ));
+ }
+}
+
+/// The documented name is the first word, without generic arguments or trailing punctuation.
+fn module_name(value: &str) -> &str {
+ let first = value.split_whitespace().next().unwrap_or_default();
+ let without_generics = first.split('<').next().unwrap_or(first);
+ without_generics.trim_end_matches(['.', ',', ':', ';'])
+}
+
+/// Exported non-pure circuits carry the constraints annotation.
+fn check_constraints(
+ declaration: &Declaration,
+ display: &Path,
+ config: &Config,
+ strict: bool,
+ findings: &mut Vec,
+) {
+ if declaration.kind != DeclKind::Circuit || !declaration.exported || declaration.pure {
+ return;
+ }
+ let Some(attached) = declaration.doc.as_ref() else {
+ return;
+ };
+ let tag = &config.constraints.tag;
+
+ let Some(occurrence) = attached.comment.first(tag) else {
+ findings.push(Finding::new(
+ display,
+ declaration.position,
+ RuleId::MISSING_CONSTRAINTS,
+ format!("{} doc comment has no {tag}", subject(declaration)),
+ ));
+ return;
+ };
+
+ // The annotation is one line; anything below it is prose, not part of the value.
+ let value = occurrence.value.lines().next().unwrap_or_default().trim();
+ let position = attached.tag_position(occurrence);
+
+ let Some(placeholders) = parse_constraints(value) else {
+ findings.push(Finding::new(
+ display,
+ position,
+ RuleId::CONSTRAINTS_FORMAT,
+ format!("{tag} value `{value}` is not `k=, rows=`"),
+ ));
+ return;
+ };
+
+ if strict && placeholders {
+ findings.push(Finding::new(
+ display,
+ position,
+ RuleId::CONSTRAINTS_PLACEHOLDER,
+ format!("{tag} value `{value}` still holds a placeholder"),
+ ));
+ }
+}
+
+/// Matches `k=,*rows=`; returns whether either field is a `?` placeholder.
+fn parse_constraints(value: &str) -> Option {
+ let rest = value.strip_prefix("k=")?;
+ let (k, rest) = rest.split_once(',')?;
+ let rows = rest.trim_start().strip_prefix("rows=")?;
+
+ let k_placeholder = is_placeholder(k)?;
+ let rows_placeholder = is_placeholder(rows)?;
+ Some(k_placeholder || rows_placeholder)
+}
+
+/// A constraints field is either a decimal count or a `?` placeholder.
+fn is_placeholder(field: &str) -> Option {
+ match field {
+ "?" => Some(true),
+ digits if !digits.is_empty() && digits.bytes().all(|byte| byte.is_ascii_digit()) => {
+ Some(false)
+ }
+ _ => None,
+ }
+}
+
+/// The kind plus the backquoted name, or a bare kind when the declaration has no name.
+fn subject(declaration: &Declaration) -> String {
+ match declaration.name.as_deref() {
+ Some(name) => format!("{} `{name}`", declaration.kind),
+ None => declaration.kind.to_string(),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{Linter, module_name, parse_constraints};
+ use crate::config::{Config, DocsPolicy};
+ use crate::doc::Tag;
+ use std::path::Path;
+
+ fn findings(source: &str, config: &Config, strict: bool) -> Vec {
+ Linter::new()
+ .expect("the bundled grammar loads")
+ .check(Path::new("a.compact"), source, config, strict)
+ .expect("the source produced a tree")
+ .iter()
+ .map(ToString::to_string)
+ .collect()
+ }
+
+ fn circuit_config() -> Config {
+ let mut config = Config::default();
+ config.kinds.circuit.tags = vec![Tag::new("@description")];
+ config
+ }
+
+ #[test]
+ fn a_non_exported_circuit_inside_an_exported_module_needs_no_docs() {
+ let source = "/** @description M. */\nexport module M {\n circuit helper(): [] { }\n}\n";
+
+ assert!(findings(source, &Config::default(), false).is_empty());
+ }
+
+ #[test]
+ fn an_exported_module_without_a_doc_comment_is_reported() {
+ let source = "export module M {\n}\n";
+
+ assert_eq!(
+ findings(source, &Config::default(), false),
+ ["a.compact:1:1: missing-doc: module `M` has no doc comment"]
+ );
+ }
+
+ #[test]
+ fn a_line_comment_between_the_doc_and_the_declaration_breaks_attachment() {
+ let source = "/** @description M. */\n// a note\nexport module M {\n}\n";
+
+ assert_eq!(
+ findings(source, &Config::default(), false),
+ ["a.compact:3:1: missing-doc: module `M` has no doc comment"]
+ );
+ }
+
+ #[test]
+ fn a_pure_exported_circuit_needs_no_constraints() {
+ let source = "/** @description Adds. */\nexport pure circuit add(a: Uint<8>): Uint<8> { return a; }\n";
+
+ assert!(findings(source, &circuit_config(), false).is_empty());
+ }
+
+ #[test]
+ fn an_exported_circuit_without_constraints_is_reported() {
+ let source =
+ "/** @description Bumps. */\nexport circuit bump(): [] { count.increment(1); }\n";
+
+ assert_eq!(
+ findings(source, &circuit_config(), false),
+ ["a.compact:2:1: missing-constraints: circuit `bump` doc comment has no @constraints"]
+ );
+ }
+
+ #[test]
+ fn a_placeholder_is_a_finding_only_under_strict() {
+ let source = "/**\n * @description Bumps.\n * @constraints k=?, rows=?\n */\nexport circuit bump(): [] { }\n";
+ let config = circuit_config();
+
+ assert!(findings(source, &config, false).is_empty());
+ assert_eq!(
+ findings(source, &config, true),
+ [
+ "a.compact:3:4: constraints-placeholder: @constraints value `k=?, rows=?` still holds a placeholder"
+ ]
+ );
+ }
+
+ #[test]
+ fn a_forbidden_tag_is_reported_even_where_docs_are_optional() {
+ let source = "/**\n * @return nothing\n */\ncircuit hidden(): [] { }\n";
+ let mut config = Config::default();
+ config.tags.forbid = vec![Tag::new("@return")];
+
+ assert_eq!(
+ findings(source, &config, false),
+ ["a.compact:2:4: forbidden-tag: forbidden tag @return"]
+ );
+ }
+
+ #[test]
+ fn a_witness_under_docs_all_is_checked_without_export() {
+ let source = "witness wit_secret(): Bytes<32>;\n";
+ let mut config = Config::default();
+ config.kinds.witness.docs = DocsPolicy::All;
+
+ assert_eq!(
+ findings(source, &config, false),
+ ["a.compact:1:1: missing-doc: witness `wit_secret` has no doc comment"]
+ );
+ }
+
+ #[test]
+ fn a_parse_defect_suppresses_the_doc_rules() {
+ let source = "export module M {\nexport circuit\n";
+
+ let reported = findings(source, &Config::default(), false);
+ assert_eq!(reported.len(), 1, "{reported:?}");
+ assert!(reported[0].contains("parse error"), "{reported:?}");
+ }
+
+ #[test]
+ fn the_constraints_tag_is_configurable() {
+ let source = "/**\n * @description Bumps.\n * @circuitInfo k=6, rows=28\n */\nexport circuit bump(): [] { }\n";
+ let mut config = circuit_config();
+ config.constraints.tag = Tag::new("@circuitInfo");
+
+ assert!(findings(source, &config, false).is_empty());
+ }
+
+ #[test]
+ fn constraints_accept_flexible_spacing_after_the_comma() {
+ assert_eq!(parse_constraints("k=10, rows=626"), Some(false));
+ assert_eq!(parse_constraints("k=10,rows=626"), Some(false));
+ assert_eq!(parse_constraints("k=?, rows=626"), Some(true));
+ assert_eq!(parse_constraints("k=10, rows=626 (approx)"), None);
+ assert_eq!(parse_constraints("rows=626, k=10"), None);
+ }
+
+ #[test]
+ fn a_documented_module_name_drops_generics_and_trailing_punctuation() {
+ assert_eq!(module_name("Signer"), "Signer");
+ assert_eq!(module_name("Utils."), "Utils");
+ assert_eq!(module_name("ShieldedToken (archived)"), "ShieldedToken");
+ }
+}
diff --git a/crates/compact-lint/tests/check.rs b/crates/compact-lint/tests/check.rs
new file mode 100644
index 0000000..452c09d
--- /dev/null
+++ b/crates/compact-lint/tests/check.rs
@@ -0,0 +1,172 @@
+//! `compact-lint check` end to end, one fixture case per rule.
+//!
+//! Every case runs with `--no-format`, because CI has no `compact` binary.
+
+use std::path::PathBuf;
+use std::process::Command;
+
+use assert_cmd::prelude::*;
+
+/// Exit code for a run that produced findings.
+const EXIT_FINDINGS: i32 = 1;
+
+/// Exit code for a usage, config, IO or parser error.
+const EXIT_ERROR: i32 = 2;
+
+fn case(name: &str) -> PathBuf {
+ // The child reports its working directory resolved, so the expectation must be too.
+ PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+ .join("tests/fixtures")
+ .join(name)
+ .canonicalize()
+ .expect("the fixture case exists")
+}
+
+fn binary() -> Command {
+ Command::cargo_bin("compact-lint").expect("the binary is built alongside this test")
+}
+
+fn expected(name: &str) -> String {
+ std::fs::read_to_string(case(name).join("expected.txt")).expect("the case has an expectation")
+}
+
+/// Runs `check` inside a case directory and returns its exit code and stdout.
+fn run(name: &str, extra: &[&str]) -> (i32, String) {
+ let output = binary()
+ .current_dir(case(name))
+ .args(["check", "--no-format"])
+ .args(extra)
+ .output()
+ .expect("the binary ran");
+
+ (
+ output.status.code().expect("the binary was not signalled"),
+ String::from_utf8(output.stdout).expect("the report is UTF-8"),
+ )
+}
+
+fn assert_case(name: &str, extra: &[&str], code: i32) {
+ let (actual_code, stdout) = run(name, extra);
+
+ assert_eq!(stdout, expected(name), "stdout for case {name}");
+ assert_eq!(actual_code, code, "exit code for case {name}");
+}
+
+#[test]
+fn a_fully_documented_module_reports_nothing() {
+ assert_case("clean", &[], 0);
+}
+
+#[test]
+fn undocumented_declarations_and_detached_docs_report_missing_doc() {
+ assert_case("missing-doc", &[], EXIT_FINDINGS);
+}
+
+#[test]
+fn a_doc_comment_without_a_required_tag_reports_missing_tag() {
+ assert_case("missing-tag", &[], EXIT_FINDINGS);
+}
+
+#[test]
+fn a_forbidden_tag_reports_wherever_it_appears() {
+ assert_case("forbidden-tag", &[], EXIT_FINDINGS);
+}
+
+#[test]
+fn a_module_tag_naming_another_module_reports_module_name() {
+ assert_case("module-name", &[], EXIT_FINDINGS);
+}
+
+#[test]
+fn an_unannotated_exported_circuit_reports_missing_constraints() {
+ assert_case("missing-constraints", &[], EXIT_FINDINGS);
+}
+
+#[test]
+fn a_malformed_constraints_value_reports_constraints_format() {
+ assert_case("constraints-format", &[], EXIT_FINDINGS);
+}
+
+#[test]
+fn a_placeholder_reports_only_under_strict() {
+ let (code, stdout) = run("constraints-placeholder", &[]);
+ assert_eq!(stdout, "");
+ assert_eq!(code, 0);
+
+ assert_case("constraints-placeholder", &["--strict"], EXIT_FINDINGS);
+}
+
+#[test]
+fn a_broken_file_reports_parse_instead_of_crashing() {
+ assert_case("parse", &[], EXIT_FINDINGS);
+}
+
+#[test]
+fn an_explicit_path_replaces_the_include_globs() {
+ let output = binary()
+ .current_dir(case("missing-doc"))
+ .args(["check", "--no-format", "Gaps.compact"])
+ .output()
+ .expect("the binary ran");
+
+ assert_eq!(
+ String::from_utf8(output.stdout).expect("the report is UTF-8"),
+ expected("missing-doc")
+ );
+}
+
+#[test]
+fn an_explicit_config_anchors_discovery_at_the_working_directory() {
+ let config = case("clean").join("compact-lint.toml");
+ let output = binary()
+ .current_dir(case("missing-doc"))
+ .args(["check", "--no-format", "--config"])
+ .arg(&config)
+ .output()
+ .expect("the binary ran");
+
+ assert_eq!(
+ String::from_utf8(output.stdout).expect("the report is UTF-8"),
+ expected("missing-doc")
+ );
+}
+
+#[test]
+fn a_missing_compact_binary_exits_two_and_names_the_escape_hatch() {
+ let output = binary()
+ .current_dir(case("clean"))
+ .args(["check", "--compact-bin", "compact-lint-no-such-binary"])
+ .output()
+ .expect("the binary ran");
+
+ let stderr = String::from_utf8(output.stderr).expect("the error is UTF-8");
+ assert_eq!(output.status.code(), Some(EXIT_ERROR), "{stderr}");
+ assert!(stderr.contains("--no-format"), "{stderr}");
+}
+
+#[test]
+fn an_unreadable_config_exits_two() {
+ let output = binary()
+ .current_dir(case("clean"))
+ .args(["check", "--no-format", "--config", "compact-lint.toml.nope"])
+ .output()
+ .expect("the binary ran");
+
+ assert_eq!(output.status.code(), Some(EXIT_ERROR));
+}
+
+#[test]
+fn the_summary_goes_to_stderr_and_the_findings_to_stdout() {
+ let output = binary()
+ .current_dir(case("module-name"))
+ .args(["check", "--no-format"])
+ .output()
+ .expect("the binary ran");
+
+ let stderr = String::from_utf8(output.stderr).expect("the summary is UTF-8");
+ assert_eq!(stderr.trim_end(), "1 findings in 1 files");
+ assert!(
+ String::from_utf8_lossy(&output.stdout).contains("module-name"),
+ "the rule id belongs on stdout"
+ );
+}
diff --git a/crates/compact-lint/tests/fixtures/clean/Clean.compact b/crates/compact-lint/tests/fixtures/clean/Clean.compact
new file mode 100644
index 0000000..030f785
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/clean/Clean.compact
@@ -0,0 +1,56 @@
+pragma language_version >= 0.23.0;
+
+/**
+ * @module Clean
+ * @description A module that satisfies every rule.
+ */
+export module Clean {
+ import CompactStandardLibrary;
+
+ /**
+ * @description Whether the module is initialized.
+ */
+ export ledger _isInitialized: Boolean;
+
+ /**
+ * @description The caller's secret key.
+ */
+ witness wit_Secret(): Bytes<32>;
+
+ /**
+ * @description A single counter reading.
+ */
+ export struct Reading {
+ value: Uint<64>
+ }
+
+ /**
+ * @description The directions a counter moves in.
+ */
+ export enum Direction {
+ Up,
+ Down
+ }
+
+ constructor() {
+ _isInitialized = false;
+ }
+
+ /**
+ * @description Doubles its argument.
+ * @param {Uint<8>} value - The value to double.
+ * @returns {Uint<16>} The doubled value.
+ */
+ export pure circuit double(value: Uint<8>): Uint<16> {
+ return value as Uint<16> * 2;
+ }
+
+ /**
+ * @description Marks the module initialized.
+ * @constraints k=10, rows=626
+ * @returns {[]} Empty tuple.
+ */
+ export circuit initialize(): [] {
+ _isInitialized = true;
+ }
+}
diff --git a/crates/compact-lint/tests/fixtures/clean/compact-lint.toml b/crates/compact-lint/tests/fixtures/clean/compact-lint.toml
new file mode 100644
index 0000000..0813e24
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/clean/compact-lint.toml
@@ -0,0 +1,27 @@
+version = 1
+include = ["**/*.compact"]
+
+[tags]
+forbid = ["@return"]
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.circuit]
+tags = ["@description"]
+
+[kinds.ledger]
+tags = ["@description"]
+
+[kinds.witness]
+docs = "all"
+tags = ["@description"]
+
+[kinds.struct]
+tags = ["@description"]
+
+[kinds.enum]
+tags = ["@description"]
+
+[kinds.constructor]
+docs = "none"
diff --git a/crates/compact-lint/tests/fixtures/clean/expected.txt b/crates/compact-lint/tests/fixtures/clean/expected.txt
new file mode 100644
index 0000000..e69de29
diff --git a/crates/compact-lint/tests/fixtures/constraints-format/Malformed.compact b/crates/compact-lint/tests/fixtures/constraints-format/Malformed.compact
new file mode 100644
index 0000000..9e2365a
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/constraints-format/Malformed.compact
@@ -0,0 +1,17 @@
+/**
+ * @module Malformed
+ * @description A module whose annotations do not parse.
+ */
+export module Malformed {
+ /**
+ * @description The comma is missing.
+ * @constraints k=10 rows=626
+ */
+ export circuit first(): [] { }
+
+ /**
+ * @description The fields are the wrong way round.
+ * @constraints rows=626, k=10
+ */
+ export circuit second(): [] { }
+}
diff --git a/crates/compact-lint/tests/fixtures/constraints-format/compact-lint.toml b/crates/compact-lint/tests/fixtures/constraints-format/compact-lint.toml
new file mode 100644
index 0000000..0813e24
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/constraints-format/compact-lint.toml
@@ -0,0 +1,27 @@
+version = 1
+include = ["**/*.compact"]
+
+[tags]
+forbid = ["@return"]
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.circuit]
+tags = ["@description"]
+
+[kinds.ledger]
+tags = ["@description"]
+
+[kinds.witness]
+docs = "all"
+tags = ["@description"]
+
+[kinds.struct]
+tags = ["@description"]
+
+[kinds.enum]
+tags = ["@description"]
+
+[kinds.constructor]
+docs = "none"
diff --git a/crates/compact-lint/tests/fixtures/constraints-format/expected.txt b/crates/compact-lint/tests/fixtures/constraints-format/expected.txt
new file mode 100644
index 0000000..a1b1ac8
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/constraints-format/expected.txt
@@ -0,0 +1,2 @@
+Malformed.compact:8:6: constraints-format: @constraints value `k=10 rows=626` is not `k=, rows=`
+Malformed.compact:14:6: constraints-format: @constraints value `rows=626, k=10` is not `k=, rows=`
diff --git a/crates/compact-lint/tests/fixtures/constraints-placeholder/Unfilled.compact b/crates/compact-lint/tests/fixtures/constraints-placeholder/Unfilled.compact
new file mode 100644
index 0000000..4b12883
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/constraints-placeholder/Unfilled.compact
@@ -0,0 +1,11 @@
+/**
+ * @module Unfilled
+ * @description A module whose annotation is still a placeholder.
+ */
+export module Unfilled {
+ /**
+ * @description Not measured yet.
+ * @constraints k=?, rows=?
+ */
+ export circuit run(): [] { }
+}
diff --git a/crates/compact-lint/tests/fixtures/constraints-placeholder/compact-lint.toml b/crates/compact-lint/tests/fixtures/constraints-placeholder/compact-lint.toml
new file mode 100644
index 0000000..0813e24
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/constraints-placeholder/compact-lint.toml
@@ -0,0 +1,27 @@
+version = 1
+include = ["**/*.compact"]
+
+[tags]
+forbid = ["@return"]
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.circuit]
+tags = ["@description"]
+
+[kinds.ledger]
+tags = ["@description"]
+
+[kinds.witness]
+docs = "all"
+tags = ["@description"]
+
+[kinds.struct]
+tags = ["@description"]
+
+[kinds.enum]
+tags = ["@description"]
+
+[kinds.constructor]
+docs = "none"
diff --git a/crates/compact-lint/tests/fixtures/constraints-placeholder/expected.txt b/crates/compact-lint/tests/fixtures/constraints-placeholder/expected.txt
new file mode 100644
index 0000000..ba3214e
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/constraints-placeholder/expected.txt
@@ -0,0 +1 @@
+Unfilled.compact:8:6: constraints-placeholder: @constraints value `k=?, rows=?` still holds a placeholder
diff --git a/crates/compact-lint/tests/fixtures/forbidden-tag/Returns.compact b/crates/compact-lint/tests/fixtures/forbidden-tag/Returns.compact
new file mode 100644
index 0000000..61836af
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/forbidden-tag/Returns.compact
@@ -0,0 +1,18 @@
+/**
+ * @module Returns
+ * @description A module that still uses the old return tag.
+ */
+export module Returns {
+ /**
+ * @description Does nothing.
+ * @constraints k=6, rows=28
+ * @return {[]} - Empty tuple.
+ */
+ export circuit run(): [] { }
+
+ /**
+ * @description Not exported, so its docs are optional, but the tag is not.
+ * @return {[]} - Empty tuple.
+ */
+ circuit helper(): [] { }
+}
diff --git a/crates/compact-lint/tests/fixtures/forbidden-tag/compact-lint.toml b/crates/compact-lint/tests/fixtures/forbidden-tag/compact-lint.toml
new file mode 100644
index 0000000..0813e24
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/forbidden-tag/compact-lint.toml
@@ -0,0 +1,27 @@
+version = 1
+include = ["**/*.compact"]
+
+[tags]
+forbid = ["@return"]
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.circuit]
+tags = ["@description"]
+
+[kinds.ledger]
+tags = ["@description"]
+
+[kinds.witness]
+docs = "all"
+tags = ["@description"]
+
+[kinds.struct]
+tags = ["@description"]
+
+[kinds.enum]
+tags = ["@description"]
+
+[kinds.constructor]
+docs = "none"
diff --git a/crates/compact-lint/tests/fixtures/forbidden-tag/expected.txt b/crates/compact-lint/tests/fixtures/forbidden-tag/expected.txt
new file mode 100644
index 0000000..0edaa79
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/forbidden-tag/expected.txt
@@ -0,0 +1,2 @@
+Returns.compact:9:6: forbidden-tag: forbidden tag @return
+Returns.compact:15:6: forbidden-tag: forbidden tag @return
diff --git a/crates/compact-lint/tests/fixtures/missing-constraints/Unmeasured.compact b/crates/compact-lint/tests/fixtures/missing-constraints/Unmeasured.compact
new file mode 100644
index 0000000..f8e0a62
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/missing-constraints/Unmeasured.compact
@@ -0,0 +1,17 @@
+/**
+ * @module Unmeasured
+ * @description A module whose exported circuit is unmeasured.
+ */
+export module Unmeasured {
+ /**
+ * @description Writes to the ledger without an annotation.
+ */
+ export circuit run(): [] { }
+
+ /**
+ * @description Pure circuits are never annotated.
+ */
+ export pure circuit double(value: Uint<8>): Uint<16> {
+ return value as Uint<16> * 2;
+ }
+}
diff --git a/crates/compact-lint/tests/fixtures/missing-constraints/compact-lint.toml b/crates/compact-lint/tests/fixtures/missing-constraints/compact-lint.toml
new file mode 100644
index 0000000..0813e24
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/missing-constraints/compact-lint.toml
@@ -0,0 +1,27 @@
+version = 1
+include = ["**/*.compact"]
+
+[tags]
+forbid = ["@return"]
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.circuit]
+tags = ["@description"]
+
+[kinds.ledger]
+tags = ["@description"]
+
+[kinds.witness]
+docs = "all"
+tags = ["@description"]
+
+[kinds.struct]
+tags = ["@description"]
+
+[kinds.enum]
+tags = ["@description"]
+
+[kinds.constructor]
+docs = "none"
diff --git a/crates/compact-lint/tests/fixtures/missing-constraints/expected.txt b/crates/compact-lint/tests/fixtures/missing-constraints/expected.txt
new file mode 100644
index 0000000..18ec688
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/missing-constraints/expected.txt
@@ -0,0 +1 @@
+Unmeasured.compact:9:3: missing-constraints: circuit `run` doc comment has no @constraints
diff --git a/crates/compact-lint/tests/fixtures/missing-doc/Gaps.compact b/crates/compact-lint/tests/fixtures/missing-doc/Gaps.compact
new file mode 100644
index 0000000..13a4d3e
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/missing-doc/Gaps.compact
@@ -0,0 +1,20 @@
+/**
+ * @module Gaps
+ * @description A module whose members are undocumented.
+ */
+export module Gaps {
+ export ledger _owner: Bytes<32>;
+
+ export enum Kind {
+ One
+ }
+
+ /**
+ * @description Detached by the line comment below it.
+ * @constraints k=6, rows=28
+ */
+ // The comment that breaks the attachment.
+ export circuit run(): [] {
+ _owner = default>;
+ }
+}
diff --git a/crates/compact-lint/tests/fixtures/missing-doc/compact-lint.toml b/crates/compact-lint/tests/fixtures/missing-doc/compact-lint.toml
new file mode 100644
index 0000000..0813e24
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/missing-doc/compact-lint.toml
@@ -0,0 +1,27 @@
+version = 1
+include = ["**/*.compact"]
+
+[tags]
+forbid = ["@return"]
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.circuit]
+tags = ["@description"]
+
+[kinds.ledger]
+tags = ["@description"]
+
+[kinds.witness]
+docs = "all"
+tags = ["@description"]
+
+[kinds.struct]
+tags = ["@description"]
+
+[kinds.enum]
+tags = ["@description"]
+
+[kinds.constructor]
+docs = "none"
diff --git a/crates/compact-lint/tests/fixtures/missing-doc/expected.txt b/crates/compact-lint/tests/fixtures/missing-doc/expected.txt
new file mode 100644
index 0000000..b3649c7
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/missing-doc/expected.txt
@@ -0,0 +1,3 @@
+Gaps.compact:6:3: missing-doc: ledger `_owner` has no doc comment
+Gaps.compact:8:3: missing-doc: enum `Kind` has no doc comment
+Gaps.compact:17:3: missing-doc: circuit `run` has no doc comment
diff --git a/crates/compact-lint/tests/fixtures/missing-tag/Untagged.compact b/crates/compact-lint/tests/fixtures/missing-tag/Untagged.compact
new file mode 100644
index 0000000..98d8116
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/missing-tag/Untagged.compact
@@ -0,0 +1,10 @@
+/**
+ * @module Untagged
+ */
+export module Untagged {
+ /**
+ * The module has no @description, and neither has this circuit.
+ * @constraints k=6, rows=28
+ */
+ export circuit run(): [] { }
+}
diff --git a/crates/compact-lint/tests/fixtures/missing-tag/compact-lint.toml b/crates/compact-lint/tests/fixtures/missing-tag/compact-lint.toml
new file mode 100644
index 0000000..0813e24
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/missing-tag/compact-lint.toml
@@ -0,0 +1,27 @@
+version = 1
+include = ["**/*.compact"]
+
+[tags]
+forbid = ["@return"]
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.circuit]
+tags = ["@description"]
+
+[kinds.ledger]
+tags = ["@description"]
+
+[kinds.witness]
+docs = "all"
+tags = ["@description"]
+
+[kinds.struct]
+tags = ["@description"]
+
+[kinds.enum]
+tags = ["@description"]
+
+[kinds.constructor]
+docs = "none"
diff --git a/crates/compact-lint/tests/fixtures/missing-tag/expected.txt b/crates/compact-lint/tests/fixtures/missing-tag/expected.txt
new file mode 100644
index 0000000..9fb4a3b
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/missing-tag/expected.txt
@@ -0,0 +1,2 @@
+Untagged.compact:4:1: missing-tag: module `Untagged` doc comment has no @description
+Untagged.compact:9:3: missing-tag: circuit `run` doc comment has no @description
diff --git a/crates/compact-lint/tests/fixtures/module-name/Renamed.compact b/crates/compact-lint/tests/fixtures/module-name/Renamed.compact
new file mode 100644
index 0000000..8ea00b6
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/module-name/Renamed.compact
@@ -0,0 +1,6 @@
+/**
+ * @module Stale
+ * @description The tag still names the module's old name.
+ */
+export module Renamed {
+}
diff --git a/crates/compact-lint/tests/fixtures/module-name/compact-lint.toml b/crates/compact-lint/tests/fixtures/module-name/compact-lint.toml
new file mode 100644
index 0000000..0813e24
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/module-name/compact-lint.toml
@@ -0,0 +1,27 @@
+version = 1
+include = ["**/*.compact"]
+
+[tags]
+forbid = ["@return"]
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.circuit]
+tags = ["@description"]
+
+[kinds.ledger]
+tags = ["@description"]
+
+[kinds.witness]
+docs = "all"
+tags = ["@description"]
+
+[kinds.struct]
+tags = ["@description"]
+
+[kinds.enum]
+tags = ["@description"]
+
+[kinds.constructor]
+docs = "none"
diff --git a/crates/compact-lint/tests/fixtures/module-name/expected.txt b/crates/compact-lint/tests/fixtures/module-name/expected.txt
new file mode 100644
index 0000000..6d31a1c
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/module-name/expected.txt
@@ -0,0 +1 @@
+Renamed.compact:2:4: module-name: @module names `Stale`, but the module is `Renamed`
diff --git a/crates/compact-lint/tests/fixtures/parse/Broken.compact b/crates/compact-lint/tests/fixtures/parse/Broken.compact
new file mode 100644
index 0000000..c21593b
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/parse/Broken.compact
@@ -0,0 +1,3 @@
+export module Broken {
+ export circuit
+}
diff --git a/crates/compact-lint/tests/fixtures/parse/compact-lint.toml b/crates/compact-lint/tests/fixtures/parse/compact-lint.toml
new file mode 100644
index 0000000..56ee774
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/parse/compact-lint.toml
@@ -0,0 +1,2 @@
+version = 1
+include = ["**/*.compact"]
diff --git a/crates/compact-lint/tests/fixtures/parse/expected.txt b/crates/compact-lint/tests/fixtures/parse/expected.txt
new file mode 100644
index 0000000..b50d9d7
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/parse/expected.txt
@@ -0,0 +1 @@
+Broken.compact:2:3: parse: parse error: unexpected ERROR
From a8ac6560535ced886a40b2acb80c13ea2dd5e0e8 Mon Sep 17 00:00:00 2001
From: 0xisk <0xisk@proton.me>
Date: Tue, 15 Sep 2026 10:15:01 +0200
Subject: [PATCH 04/14] feat(compact-lint): add the fix command
Adds fix, which rewrites doc comments so a following check is clean
for every rule it covers and a second fix is a no-op. Detection now
yields a structured Issue that check renders as a Finding and fix
renders as an Edit, so the two subcommands cannot disagree.
* fix: doc skeletons above undocumented declarations, missing tags in
config order (a missing @description adopts prose that opens the
body), the constraints placeholder after the description block,
tag renames from the new [tags] rename map, and @module name
repairs. Edits on one comment merge into a single replacement;
files are rewritten atomically with their line ending and
permissions kept.
* target: config and path resolution shared by check and fix.
* config: [fix] placeholder and [tags] rename, both validated.
* tests: one before/after fixture per rule through the binary, run
on a temp copy and followed by check and a second fix.
---
Cargo.lock | 84 +++
Cargo.toml | 1 +
crates/compact-lint/Cargo.toml | 1 +
crates/compact-lint/README.md | 66 +-
.../examples/compact-contracts.toml | 1 +
crates/compact-lint/src/check.rs | 53 +-
crates/compact-lint/src/config.rs | 67 +-
crates/compact-lint/src/edit.rs | 570 ++++++++++++++++++
crates/compact-lint/src/fix.rs | 377 ++++++++++++
crates/compact-lint/src/lib.rs | 6 +-
crates/compact-lint/src/main.rs | 74 ++-
crates/compact-lint/src/model.rs | 7 +
crates/compact-lint/src/rules.rs | 306 +++++++---
crates/compact-lint/src/target.rs | 65 ++
crates/compact-lint/tests/fix.rs | 223 +++++++
.../fix-constraints/after/Unmeasured.compact | 37 ++
.../fix-constraints/before/Unmeasured.compact | 31 +
.../fix-constraints/compact-lint.toml | 13 +
.../fixtures/fix-constraints/expected.txt | 3 +
.../fixtures/fix-crlf/after/Windows.compact | 19 +
.../fixtures/fix-crlf/before/Windows.compact | 13 +
.../tests/fixtures/fix-crlf/compact-lint.toml | 18 +
.../tests/fixtures/fix-crlf/expected.txt | 3 +
.../fix-missing-tag/after/Untagged.compact | 25 +
.../fix-missing-tag/before/Untagged.compact | 18 +
.../fix-missing-tag/compact-lint.toml | 14 +
.../fixtures/fix-missing-tag/expected.txt | 5 +
.../fix-module-name/after/Renamed.compact | 12 +
.../fix-module-name/before/Renamed.compact | 12 +
.../fix-module-name/compact-lint.toml | 8 +
.../fixtures/fix-module-name/expected.txt | 2 +
.../fixtures/fix-rename/after/Returns.compact | 19 +
.../fix-rename/before/Returns.compact | 19 +
.../fixtures/fix-rename/compact-lint.toml | 15 +
.../tests/fixtures/fix-rename/expected.txt | 2 +
.../fixtures/fix-skeleton/after/Bare.compact | 59 ++
.../fixtures/fix-skeleton/before/Bare.compact | 32 +
.../fixtures/fix-skeleton/compact-lint.toml | 31 +
.../tests/fixtures/fix-skeleton/expected.txt | 8 +
.../fix-unfixable/after/Malformed.compact | 12 +
.../fix-unfixable/before/Malformed.compact | 12 +
.../fixtures/fix-unfixable/compact-lint.toml | 15 +
.../tests/fixtures/fix-unfixable/expected.txt | 0
43 files changed, 2224 insertions(+), 134 deletions(-)
create mode 100644 crates/compact-lint/src/edit.rs
create mode 100644 crates/compact-lint/src/fix.rs
create mode 100644 crates/compact-lint/src/target.rs
create mode 100644 crates/compact-lint/tests/fix.rs
create mode 100644 crates/compact-lint/tests/fixtures/fix-constraints/after/Unmeasured.compact
create mode 100644 crates/compact-lint/tests/fixtures/fix-constraints/before/Unmeasured.compact
create mode 100644 crates/compact-lint/tests/fixtures/fix-constraints/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/fix-constraints/expected.txt
create mode 100644 crates/compact-lint/tests/fixtures/fix-crlf/after/Windows.compact
create mode 100644 crates/compact-lint/tests/fixtures/fix-crlf/before/Windows.compact
create mode 100644 crates/compact-lint/tests/fixtures/fix-crlf/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/fix-crlf/expected.txt
create mode 100644 crates/compact-lint/tests/fixtures/fix-missing-tag/after/Untagged.compact
create mode 100644 crates/compact-lint/tests/fixtures/fix-missing-tag/before/Untagged.compact
create mode 100644 crates/compact-lint/tests/fixtures/fix-missing-tag/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/fix-missing-tag/expected.txt
create mode 100644 crates/compact-lint/tests/fixtures/fix-module-name/after/Renamed.compact
create mode 100644 crates/compact-lint/tests/fixtures/fix-module-name/before/Renamed.compact
create mode 100644 crates/compact-lint/tests/fixtures/fix-module-name/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/fix-module-name/expected.txt
create mode 100644 crates/compact-lint/tests/fixtures/fix-rename/after/Returns.compact
create mode 100644 crates/compact-lint/tests/fixtures/fix-rename/before/Returns.compact
create mode 100644 crates/compact-lint/tests/fixtures/fix-rename/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/fix-rename/expected.txt
create mode 100644 crates/compact-lint/tests/fixtures/fix-skeleton/after/Bare.compact
create mode 100644 crates/compact-lint/tests/fixtures/fix-skeleton/before/Bare.compact
create mode 100644 crates/compact-lint/tests/fixtures/fix-skeleton/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/fix-skeleton/expected.txt
create mode 100644 crates/compact-lint/tests/fixtures/fix-unfixable/after/Malformed.compact
create mode 100644 crates/compact-lint/tests/fixtures/fix-unfixable/before/Malformed.compact
create mode 100644 crates/compact-lint/tests/fixtures/fix-unfixable/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/fix-unfixable/expected.txt
diff --git a/Cargo.lock b/Cargo.lock
index 80f353a..7246e94 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -82,6 +82,12 @@ dependencies = [
"wait-timeout",
]
+[[package]]
+name = "bitflags"
+version = "2.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
+
[[package]]
name = "bstr"
version = "1.13.1"
@@ -103,6 +109,12 @@ dependencies = [
"shlex",
]
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
[[package]]
name = "clap"
version = "4.6.7"
@@ -159,6 +171,7 @@ dependencies = [
"compact-tree-sitter",
"globset",
"serde",
+ "tempfile",
"thiserror",
"toml",
"tree-sitter",
@@ -186,12 +199,39 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
+
[[package]]
name = "find-msvc-tools"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
+[[package]]
+name = "getrandom"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+]
+
[[package]]
name = "globset"
version = "0.4.20"
@@ -245,6 +285,12 @@ version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
[[package]]
name = "log"
version = "0.4.34"
@@ -257,6 +303,12 @@ version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
@@ -308,6 +360,12 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
[[package]]
name = "regex"
version = "1.13.1"
@@ -337,6 +395,19 @@ version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
[[package]]
name = "same-file"
version = "1.0.6"
@@ -428,6 +499,19 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "tempfile"
+version = "3.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
+dependencies = [
+ "fastrand",
+ "getrandom",
+ "once_cell",
+ "rustix",
+ "windows-sys",
+]
+
[[package]]
name = "termtree"
version = "0.5.1"
diff --git a/Cargo.toml b/Cargo.toml
index 5e096be..753653f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,3 +25,4 @@ tree-sitter-language = "0.1"
walkdir = "2.5"
assert_cmd = "2.2"
+tempfile = "3.27"
diff --git a/crates/compact-lint/Cargo.toml b/crates/compact-lint/Cargo.toml
index 0983475..384ce97 100644
--- a/crates/compact-lint/Cargo.toml
+++ b/crates/compact-lint/Cargo.toml
@@ -31,3 +31,4 @@ walkdir.workspace = true
[dev-dependencies]
assert_cmd.workspace = true
+tempfile.workspace = true
diff --git a/crates/compact-lint/README.md b/crates/compact-lint/README.md
index 28bd628..0c4f3b2 100644
--- a/crates/compact-lint/README.md
+++ b/crates/compact-lint/README.md
@@ -2,12 +2,14 @@
Doc-comment linter for Compact sources. It parses `.compact` files with
[`compact-tree-sitter`](../compact-tree-sitter), matches every declaration against the
-per-kind template in `compact-lint.toml`, and prints one finding per line.
+per-kind template in `compact-lint.toml`, and prints one finding per line. `fix`
+rewrites the comments the repairable rules report.
## Usage
```sh
compact-lint check [PATHS]... [--config ] [--strict] [--no-format] [--compact-bin ]
+compact-lint fix [PATHS]... [--config ] [--dry-run]
```
- `PATHS` — files or directories, walked recursively for `.compact`. Skipped while
@@ -32,6 +34,52 @@ summary goes to stderr, where `M` is the number of files checked.
- `1` — findings.
- `2` — usage, config, IO or parser error.
+## Fix
+
+`fix` rewrites `.compact` files so a following `check` is clean for every rule it
+covers. It runs the same detection pass as `check`, so the two never disagree.
+
+```sh
+compact-lint fix [PATHS]... [--config ] [--dry-run]
+```
+
+- `PATHS` and `--config` resolve exactly as they do for `check`.
+- `--dry-run` — report the edits and write nothing. Exit `1` when there are edits, so
+ CI gates on it the way it gates on `cargo fmt --check`.
+
+Edits go to stdout, one per line, in `path:line:col: fix: ` form. The
+`N edits in M files` summary goes to stderr, where `M` is the number of files changed.
+
+Exit codes:
+
+- `0` — the files were written, including when there was nothing to do.
+- `1` — `--dry-run` only: edits would be made.
+- `2` — usage, config, IO or parser error.
+
+What each rule's fix does:
+
+| Rule | Fix |
+| --- | --- |
+| `missing-doc` | Inserts a doc skeleton above the declaration, indented to it. Body lines come from `kinds..tags` in config order; `@module` takes the declaration's name, every other tag takes `fix.placeholder`. Exported non-pure circuits also get the constraints line. A kind with neither gets a single placeholder line. |
+| `missing-tag` | Inserts each missing tag after the block of the last tag that precedes it in `kinds..tags`, else at the top of the body. A missing `@description` adopts untagged prose that opens the body instead of writing a placeholder. A `/** … */` one-liner expands to the multi-line form first. |
+| `missing-constraints` | Inserts ` k=?, rows=?` after the `@description` block, with a blank ` *` line before it and, where prose follows, one after. Without a `@description` it opens the body. |
+| `forbidden-tag` | Renames the tag in place, and only where `tags.rename` maps it. |
+| `module-name` | Replaces the first word of the `@module` value with the module's name, keeping what follows. |
+
+Not fixed:
+
+- `constraints-format` — a human wrote a measurement the rule cannot parse; guessing at
+ it would lose the value.
+- `constraints-placeholder` — filling `k=?` needs a real measurement.
+- `parse` — the file does not parse, so no rewrite is trustworthy.
+- `format` — that is `compact format`'s job.
+- A forbidden tag with no `tags.rename` entry stays a `check` finding.
+
+Existing lines are never reordered or reflowed. Edits are computed against the original
+text and applied from the highest offset down; several edits on one comment merge into a
+single replacement of it. Files are rewritten through a sibling `.tmp` file and renamed
+into place, keeping the file's line ending and its trailing newline, or lack of one.
+
## Rules
| Rule | Meaning |
@@ -79,6 +127,10 @@ tag = "@constraints"
[tags]
forbid = ["@return"]
+rename = { "@return" = "@returns" }
+
+[fix]
+placeholder = "TODO"
[kinds.module]
docs = "exported"
@@ -94,6 +146,8 @@ Defaults when no config file is found:
| `exclude` | `[]` |
| `constraints.tag` | `"@constraints"` |
| `tags.forbid` | `[]` |
+| `tags.rename` | `{}` |
+| `fix.placeholder` | `"TODO"` |
| `kinds..docs` | `"exported"` |
| `kinds..tags` | `[]` |
@@ -105,6 +159,10 @@ Defaults when no config file is found:
`"all"` or `"none"` there.
- `exclude` filters files found by walking a directory. A file named on the command
line is always checked.
+- `tags.rename` maps a forbidden tag to the tag `fix` writes in its place. Both spellings
+ are validated like every other tag. An unmapped forbidden tag is reported, never
+ rewritten.
+- `fix.placeholder` is the text `fix` writes where it has no value of its own.
`examples/compact-contracts.toml` is the config for OpenZeppelin/compact-contracts.
@@ -123,6 +181,10 @@ Defaults when no config file is found:
cargo test --workspace
```
-`tests/fixtures//` holds one case per rule plus a clean case: a
+`tests/fixtures//` holds one `check` case per rule plus a clean case: a
`compact-lint.toml`, the `.compact` sources, and `expected.txt`, the exact stdout with
paths relative to the case directory.
+
+`tests/fixtures/fix-/` holds one `fix` case per rule: a `compact-lint.toml`,
+`before/`, `after/` and `expected.txt`. The run copies `before/` into a temporary
+directory, so the fixtures are never rewritten in place.
diff --git a/crates/compact-lint/examples/compact-contracts.toml b/crates/compact-lint/examples/compact-contracts.toml
index 402186d..d20c3a9 100644
--- a/crates/compact-lint/examples/compact-contracts.toml
+++ b/crates/compact-lint/examples/compact-contracts.toml
@@ -11,6 +11,7 @@ tag = "@constraints"
[tags]
forbid = ["@return"]
+rename = { "@return" = "@returns" }
[kinds.module]
docs = "exported"
diff --git a/crates/compact-lint/src/check.rs b/crates/compact-lint/src/check.rs
index 2f2d2a8..7fbc195 100644
--- a/crates/compact-lint/src/check.rs
+++ b/crates/compact-lint/src/check.rs
@@ -1,22 +1,19 @@
-//! The `check` subcommand: resolve config, collect files, run the rules.
+//! The `check` subcommand: resolve the target, run the rules, add the format pass.
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use thiserror::Error;
-use crate::config::{Config, ConfigError, discover as discover_config};
-use crate::discover::{self, DiscoverError};
use crate::format::{self, FormatError};
use crate::report::Finding;
use crate::rules::{LintError, Linter};
+use crate::target::{self, TargetError};
#[derive(Debug, Error)]
pub enum CheckError {
#[error(transparent)]
- Config(#[from] ConfigError),
- #[error(transparent)]
- Discover(#[from] DiscoverError),
+ Target(#[from] TargetError),
#[error(transparent)]
Lint(#[from] LintError),
#[error(transparent)]
@@ -59,59 +56,25 @@ impl Outcome {
/// # Errors
/// Returns an error when the config, the file walk, the parser or the formatter fails.
pub fn run(options: &Options, cwd: &Path) -> Result {
- let (config, config_path, base) = resolve_config(options, cwd)?;
- let source = config_path.as_deref().unwrap_or(Path::new(""));
-
- let exclude = config.exclude_set(source)?;
- let files = if options.paths.is_empty() {
- let include = config.include_set(source)?;
- discover::from_globs(&base, &include, &exclude)?
- } else {
- discover::from_paths(&options.paths, &base, &exclude)?
- };
- let files: Vec = files.iter().map(|path| display_path(path, cwd)).collect();
+ let target = target::resolve(&options.paths, options.config_path.as_deref(), cwd)?;
let mut linter = Linter::new()?;
let mut findings = Vec::new();
- for path in &files {
+ for path in &target.files {
let text = std::fs::read_to_string(path).map_err(|source| CheckError::Read {
path: path.clone(),
source,
})?;
- findings.extend(linter.check(path, &text, &config, options.strict)?);
+ findings.extend(linter.check(path, &text, &target.config, options.strict)?);
}
if !options.no_format {
- findings.extend(format::check(&options.compact_bin, &files)?);
+ findings.extend(format::check(&options.compact_bin, &target.files)?);
}
findings.sort_by(|left, right| left.sort_key().cmp(&right.sort_key()));
Ok(Outcome {
findings,
- files_checked: files.len(),
+ files_checked: target.files.len(),
})
}
-
-/// An explicit `--config` is a shareable preset, so glob discovery stays anchored at
-/// `cwd`; a config found by walking upward anchors discovery at its own directory.
-fn resolve_config(
- options: &Options,
- cwd: &Path,
-) -> Result<(Config, Option, PathBuf), ConfigError> {
- if let Some(path) = options.config_path.as_deref() {
- return Ok((Config::load(path)?, Some(path.to_owned()), cwd.to_owned()));
- }
-
- match discover_config(cwd) {
- Some(path) => {
- let base = path.parent().unwrap_or(cwd).to_owned();
- Ok((Config::load(&path)?, Some(path), base))
- }
- None => Ok((Config::default(), None, cwd.to_owned())),
- }
-}
-
-/// Paths under the working directory print relative to it; anything else prints as is.
-fn display_path(path: &Path, cwd: &Path) -> PathBuf {
- path.strip_prefix(cwd).unwrap_or(path).to_owned()
-}
diff --git a/crates/compact-lint/src/config.rs b/crates/compact-lint/src/config.rs
index 0d7ae2b..002c6d1 100644
--- a/crates/compact-lint/src/config.rs
+++ b/crates/compact-lint/src/config.rs
@@ -1,5 +1,6 @@
//! `compact-lint.toml`: discovery, parsing, and the built-in defaults.
+use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use globset::{Glob, GlobSet, GlobSetBuilder};
@@ -117,11 +118,28 @@ impl Default for ConstraintsConfig {
}
}
-/// Tags no doc comment may carry.
+/// Tags no doc comment may carry, and the replacements `fix` writes for them.
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields, default)]
pub struct TagsConfig {
pub forbid: Vec,
+ /// Forbidden tag to its replacement; an unmapped forbidden tag stays a finding.
+ pub rename: BTreeMap,
+}
+
+/// What `fix` writes where it has no value to write.
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+#[serde(deny_unknown_fields, default)]
+pub struct FixConfig {
+ pub placeholder: String,
+}
+
+impl Default for FixConfig {
+ fn default() -> Self {
+ Self {
+ placeholder: "TODO".to_owned(),
+ }
+ }
}
/// A parsed `compact-lint.toml`, or the built-in defaults when no file was found.
@@ -133,6 +151,7 @@ pub struct Config {
pub exclude: Vec,
pub constraints: ConstraintsConfig,
pub tags: TagsConfig,
+ pub fix: FixConfig,
pub kinds: KindsConfig,
}
@@ -144,6 +163,7 @@ impl Default for Config {
exclude: Vec::new(),
constraints: ConstraintsConfig::default(),
tags: TagsConfig::default(),
+ fix: FixConfig::default(),
kinds: KindsConfig::default(),
}
}
@@ -199,9 +219,17 @@ impl Config {
per_kind
.chain(self.tags.forbid.iter())
+ .chain(self.tags.rename.keys())
+ .chain(self.tags.rename.values())
.chain(std::iter::once(&self.constraints.tag))
}
+ /// The replacement `fix` writes for a forbidden tag, when the config names one.
+ #[must_use]
+ pub fn rename_of(&self, tag: &Tag) -> Option<&Tag> {
+ self.tags.rename.get(tag)
+ }
+
/// Compiles the `include` globs.
/// # Errors
/// Returns an error when a pattern is not a valid glob.
@@ -295,6 +323,43 @@ mod tests {
std::fs::remove_dir_all(&dir).expect("the temp dir is removable");
}
+ #[test]
+ fn a_rename_maps_a_forbidden_tag_to_its_replacement() {
+ let config: Config = toml::from_str(
+ "[tags]\nforbid = [\"@return\"]\nrename = { \"@return\" = \"@returns\" }\n",
+ )
+ .expect("the snippet is valid config");
+
+ assert_eq!(
+ config.rename_of(&Tag::new("@return")),
+ Some(&Tag::new("@returns"))
+ );
+ assert_eq!(config.rename_of(&Tag::new("@notice")), None);
+ }
+
+ #[test]
+ fn a_rename_target_without_its_at_sign_is_rejected() {
+ let dir = std::env::temp_dir().join(format!("compact-lint-rename-{}", std::process::id()));
+ std::fs::create_dir_all(&dir).expect("the temp dir is writable");
+ let path = dir.join("compact-lint.toml");
+ std::fs::write(&path, "[tags]\nrename = { \"@return\" = \"returns\" }\n")
+ .expect("the config is writable");
+
+ let error = Config::load(&path).expect_err("the replacement lacks its @");
+
+ assert!(error.to_string().contains("\"returns\""), "{error}");
+ std::fs::remove_dir_all(&dir).expect("the temp dir is removable");
+ }
+
+ #[test]
+ fn the_fix_placeholder_defaults_to_todo() {
+ assert_eq!(Config::default().fix.placeholder, "TODO");
+
+ let config: Config = toml::from_str("[fix]\nplaceholder = \"FIXME\"\n")
+ .expect("the snippet is valid config");
+ assert_eq!(config.fix.placeholder, "FIXME");
+ }
+
#[test]
fn an_unknown_kind_is_rejected() {
let error =
diff --git a/crates/compact-lint/src/edit.rs b/crates/compact-lint/src/edit.rs
new file mode 100644
index 0000000..281d859
--- /dev/null
+++ b/crates/compact-lint/src/edit.rs
@@ -0,0 +1,570 @@
+//! Byte-range edits and the doc-comment rewriting `fix` applies.
+//!
+//! Every edit is computed against the original text. They are applied from the highest
+//! offset down, so positions never shift, and edits that target the same doc comment are
+//! merged into one replacement of that comment.
+
+use std::ops::Range;
+
+use crate::report::Position;
+
+/// One repair, carrying the line `fix` prints for it.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Edit {
+ pub position: Position,
+ pub message: String,
+ pub kind: EditKind,
+}
+
+/// What an edit rewrites.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum EditKind {
+ /// Text inserted at a byte offset, used for a whole doc-comment skeleton.
+ Insert { offset: usize, text: String },
+ /// One rewrite of the doc comment spanning `range`.
+ Doc {
+ range: Range,
+ /// Column the `/**` starts on, the indentation of every line the op writes.
+ indent: usize,
+ op: DocOp,
+ },
+}
+
+/// One change to the lines of a doc comment.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum DocOp {
+ /// A `@tag value` line added after the last of `after` present, else at the top of the body.
+ InsertTag {
+ line: String,
+ /// Tags that precede this one in config order.
+ after: Vec,
+ /// Untagged prose opening the body becomes this tag's value instead of the placeholder.
+ adopts_prose: bool,
+ },
+ /// A `@tag k=?, rows=?` line added after the `@description` block.
+ InsertConstraints(String),
+ /// The tag name on one line replaced in place.
+ RenameTag {
+ /// 0-based line index inside the original comment.
+ line: usize,
+ from: String,
+ to: String,
+ },
+ /// The first word of `@module`'s value replaced with the module's name.
+ SetModuleName {
+ /// 0-based line index inside the original comment.
+ line: usize,
+ name: String,
+ },
+}
+
+/// The line ending the file already uses, taken from its first line.
+#[must_use]
+pub fn newline_of(source: &str) -> &'static str {
+ match source.find('\n') {
+ Some(index) if index > 0 && source.as_bytes()[index - 1] == b'\r' => "\r\n",
+ _ => "\n",
+ }
+}
+
+/// Rewrites `source` with every edit, merging the edits that share a doc comment.
+#[must_use]
+pub fn apply(source: &str, edits: &[Edit], newline: &str) -> String {
+ let mut replacements: Vec<(Range, String)> = Vec::new();
+ let mut docs: Vec<(Range, usize, Vec<&DocOp>)> = Vec::new();
+
+ for edit in edits {
+ match &edit.kind {
+ EditKind::Insert { offset, text } => {
+ replacements.push((*offset..*offset, text.clone()));
+ }
+ EditKind::Doc { range, indent, op } => {
+ match docs.iter_mut().find(|(known, _, _)| known == range) {
+ Some((_, _, ops)) => ops.push(op),
+ None => docs.push((range.clone(), *indent, vec![op])),
+ }
+ }
+ }
+ }
+
+ for (range, indent, ops) in docs {
+ let original = source.get(range.clone()).unwrap_or_default();
+ replacements.push((range, render_doc(original, indent, newline, &ops)));
+ }
+
+ replacements.sort_by_key(|(range, _)| std::cmp::Reverse(range.start));
+
+ let mut out = source.to_owned();
+ for (range, text) in replacements {
+ if range.end <= out.len() {
+ out.replace_range(range, &text);
+ }
+ }
+ out
+}
+
+/// Applies every op to one doc comment and renders it back with `newline`.
+#[must_use]
+fn render_doc(doc: &str, indent: usize, newline: &str, ops: &[&DocOp]) -> String {
+ let mut lines: Vec = doc
+ .split('\n')
+ .map(|line| line.strip_suffix('\r').unwrap_or(line).to_owned())
+ .collect();
+
+ let margin = " ".repeat(indent);
+ let gutter = format!("{margin} * ");
+
+ // An insertion needs a body, so a `/** … */` one-liner expands first.
+ let expands = lines.len() == 1
+ && ops
+ .iter()
+ .any(|op| matches!(op, DocOp::InsertTag { .. } | DocOp::InsertConstraints(_)));
+ let shift = usize::from(expands);
+ if expands {
+ lines = expand(&lines[0], &margin, &gutter);
+ }
+
+ // In-place ops address the original lines, so they run before any insertion moves
+ // them; tags precede the constraints line, which anchors itself on `@description`.
+ for op in ops {
+ match op {
+ DocOp::RenameTag { line, from, to } => {
+ if let Some(target) = lines.get_mut(line + shift)
+ && let Some(renamed) = rename_tag(target, from, to)
+ {
+ *target = renamed;
+ }
+ }
+ DocOp::SetModuleName { line, name } => {
+ if let Some(target) = lines.get_mut(line + shift)
+ && let Some(renamed) = set_module_name(target, name)
+ {
+ *target = renamed;
+ }
+ }
+ _ => {}
+ }
+ }
+
+ for op in ops {
+ if let DocOp::InsertTag {
+ line,
+ after,
+ adopts_prose,
+ } = op
+ {
+ insert_tag(&mut lines, &gutter, line, after, *adopts_prose);
+ }
+ }
+
+ for op in ops {
+ if let DocOp::InsertConstraints(body) = op {
+ insert_constraints(&mut lines, &margin, &gutter, body);
+ }
+ }
+
+ lines.join(newline)
+}
+
+/// Splits `/** text */` into the three-line form, keeping the comment's indentation.
+fn expand(single: &str, margin: &str, gutter: &str) -> Vec {
+ let inner = single
+ .strip_prefix("/**")
+ .and_then(|rest| rest.strip_suffix("*/"))
+ .unwrap_or(single)
+ .trim();
+
+ vec![
+ "/**".to_owned(),
+ format!("{gutter}{inner}"),
+ format!("{margin} */"),
+ ]
+}
+
+/// A tag line lands after the block of the last tag in `after` that is present, else at
+/// the top of the body. With `adopts_prose`, untagged prose opening the body takes the
+/// tag instead of a new line going in.
+fn insert_tag(
+ lines: &mut Vec,
+ gutter: &str,
+ line: &str,
+ after: &[String],
+ adopts_prose: bool,
+) {
+ let closer = lines.len().saturating_sub(1);
+
+ if adopts_prose
+ && let Some(first) = (1..closer).find(|index| !is_blank(&lines[*index]))
+ && !starts_tag(content(&lines[first]))
+ && let Some((tag, _)) = line.split_once(' ')
+ {
+ lines[first] = format!("{gutter}{tag} {}", content(&lines[first]));
+ return;
+ }
+
+ let at = tag_block_end(lines, closer, after).map_or(1.min(closer), |end| end + 1);
+ lines.insert(at, format!("{gutter}{line}"));
+}
+
+/// The constraints line follows the `@description` block, or opens the body without one.
+fn insert_constraints(lines: &mut Vec, margin: &str, gutter: &str, body: &str) {
+ let closer = lines.len().saturating_sub(1);
+ let blank = format!("{margin} *");
+
+ let at = match description_end(lines, closer) {
+ Some(end) => {
+ lines.insert(end + 1, blank.clone());
+ end + 2
+ }
+ None => 1.min(closer),
+ };
+
+ lines.insert(at, format!("{gutter}{body}"));
+
+ let follows = at + 1;
+ if follows < lines.len() && follows != lines.len() - 1 && !is_blank(&lines[follows]) {
+ lines.insert(follows, blank);
+ }
+}
+
+/// The last line of the `@description` block: its tag line plus every continuation.
+fn description_end(lines: &[String], closer: usize) -> Option {
+ tag_block_end(lines, closer, &["@description".to_owned()])
+}
+
+/// The last line of the block opened by the last occurrence of any tag in `tags`.
+fn tag_block_end(lines: &[String], closer: usize, tags: &[String]) -> Option {
+ let start = (1..closer).rev().find(|index| {
+ let text = content(&lines[*index]);
+ tags.iter().any(|tag| {
+ text.strip_prefix(tag.as_str())
+ .is_some_and(|rest| rest.is_empty() || rest.starts_with(char::is_whitespace))
+ })
+ })?;
+
+ let mut end = start;
+ while end + 1 < closer {
+ let next = content(&lines[end + 1]);
+ if next.is_empty() || starts_tag(next) {
+ break;
+ }
+ end += 1;
+ }
+ Some(end)
+}
+
+/// A comment line without its indentation, its `*` and the space after it.
+fn content(line: &str) -> &str {
+ let trimmed = line.trim_start();
+ match trimmed.strip_prefix('*') {
+ Some(rest) => rest.trim(),
+ None => trimmed.trim(),
+ }
+}
+
+fn is_blank(line: &str) -> bool {
+ content(line).is_empty()
+}
+
+fn starts_tag(content: &str) -> bool {
+ content
+ .strip_prefix('@')
+ .is_some_and(|rest| rest.starts_with(|character: char| character.is_ascii_alphabetic()))
+}
+
+/// Replaces the first whole-word occurrence of `from`, so `@return` never claims `@returns`.
+fn rename_tag(line: &str, from: &str, to: &str) -> Option {
+ let bytes = line.as_bytes();
+ let mut search = 0;
+ while let Some(offset) = line.get(search..)?.find(from) {
+ let start = search + offset;
+ let end = start + from.len();
+ let before_is_word = start > 0 && bytes[start - 1].is_ascii_alphanumeric();
+ let after_is_word = bytes.get(end).is_some_and(u8::is_ascii_alphabetic);
+ if !before_is_word && !after_is_word {
+ return Some(format!("{}{to}{}", &line[..start], &line[end..]));
+ }
+ search = end;
+ }
+ None
+}
+
+/// Replaces the first word after `@module`, keeping whatever follows it.
+fn set_module_name(line: &str, name: &str) -> Option {
+ let tag = "@module";
+ let start = line.find(tag)?;
+ let after = start + tag.len();
+ let rest = line.get(after..)?;
+
+ let spaces = rest.len() - rest.trim_start().len();
+ let value = rest.trim_start();
+ let word = value
+ .find(char::is_whitespace)
+ .map_or(value.len(), |index| index);
+
+ if value.is_empty() {
+ return Some(format!("{line} {name}"));
+ }
+ Some(format!(
+ "{}{}{name}{}",
+ &line[..after],
+ &rest[..spaces],
+ &value[word..]
+ ))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{
+ DocOp, Edit, EditKind, apply, newline_of, rename_tag, render_doc, set_module_name,
+ };
+ use crate::report::Position;
+
+ fn doc_edit(range: std::ops::Range, indent: usize, op: DocOp) -> Edit {
+ Edit {
+ position: Position::file_start(),
+ message: String::new(),
+ kind: EditKind::Doc { range, indent, op },
+ }
+ }
+
+ fn tag(line: &str, after: &[&str]) -> DocOp {
+ DocOp::InsertTag {
+ line: line.to_owned(),
+ after: after.iter().map(ToString::to_string).collect(),
+ adopts_prose: line.starts_with("@description"),
+ }
+ }
+
+ fn rendered(doc: &str, indent: usize, ops: &[DocOp]) -> String {
+ let borrowed: Vec<&DocOp> = ops.iter().collect();
+ render_doc(doc, indent, "\n", &borrowed)
+ }
+
+ #[test]
+ fn a_tag_goes_in_at_the_top_of_the_body() {
+ let doc = "/**\n * @constraints k=6, rows=28\n */";
+
+ assert_eq!(
+ rendered(doc, 2, &[tag("@description TODO", &[])]),
+ "/**\n * @description TODO\n * @constraints k=6, rows=28\n */"
+ );
+ }
+
+ #[test]
+ fn several_tags_keep_the_order_they_were_produced_in() {
+ let doc = "/**\n * @notice loose\n */";
+ let ops = [
+ tag("@module Ownable", &[]),
+ tag("@description TODO", &["@module"]),
+ ];
+
+ assert_eq!(
+ rendered(doc, 0, &ops),
+ "/**\n * @module Ownable\n * @description TODO\n * @notice loose\n */"
+ );
+ }
+
+ #[test]
+ fn a_tag_lands_after_the_tags_that_precede_it_in_config_order() {
+ let doc = "/**\n * @module Ownable\n * spanning two lines\n *\n * @notice loose\n */";
+
+ assert_eq!(
+ rendered(doc, 0, &[tag("@description TODO", &["@module"])]),
+ "/**\n * @module Ownable\n * spanning two lines\n * @description TODO\n *\n * @notice loose\n */"
+ );
+ }
+
+ #[test]
+ fn a_description_adopts_the_prose_that_opens_the_body() {
+ let doc = "/**\n * Approved accounts.\n * @param {T} a - A.\n */";
+
+ assert_eq!(
+ rendered(doc, 0, &[tag("@description TODO", &[])]),
+ "/**\n * @description Approved accounts.\n * @param {T} a - A.\n */"
+ );
+ assert_eq!(
+ rendered(
+ "/** Approved accounts. */",
+ 2,
+ &[tag("@description TODO", &[])]
+ ),
+ "/**\n * @description Approved accounts.\n */"
+ );
+ }
+
+ #[test]
+ fn a_single_line_doc_expands_before_a_tag_goes_in() {
+ let doc = "/** @description Approved accounts. */";
+
+ assert_eq!(
+ rendered(doc, 2, &[tag("@module M", &[])]),
+ "/**\n * @module M\n * @description Approved accounts.\n */"
+ );
+ }
+
+ #[test]
+ fn constraints_follow_the_description_and_its_continuation_lines() {
+ let doc = "/**\n * @description Sends a coin\n * to the recipient.\n * @param {Coin} coin - The coin.\n */";
+
+ assert_eq!(
+ rendered(
+ doc,
+ 0,
+ &[DocOp::InsertConstraints(
+ "@constraints k=?, rows=?".to_owned()
+ )]
+ ),
+ "/**\n * @description Sends a coin\n * to the recipient.\n *\n * @constraints k=?, rows=?\n *\n * @param {Coin} coin - The coin.\n */"
+ );
+ }
+
+ #[test]
+ fn constraints_need_no_trailing_blank_before_the_closer() {
+ let doc = "/**\n * @description Bumps.\n */";
+
+ assert_eq!(
+ rendered(
+ doc,
+ 0,
+ &[DocOp::InsertConstraints(
+ "@constraints k=?, rows=?".to_owned()
+ )]
+ ),
+ "/**\n * @description Bumps.\n *\n * @constraints k=?, rows=?\n */"
+ );
+ }
+
+ #[test]
+ fn constraints_open_the_body_when_there_is_no_description() {
+ let doc = "/**\n * @notice loose prose.\n */";
+
+ assert_eq!(
+ rendered(
+ doc,
+ 0,
+ &[DocOp::InsertConstraints(
+ "@constraints k=?, rows=?".to_owned()
+ )]
+ ),
+ "/**\n * @constraints k=?, rows=?\n *\n * @notice loose prose.\n */"
+ );
+ }
+
+ #[test]
+ fn a_missing_description_and_missing_constraints_compose_on_one_comment() {
+ let doc = "/**\n * @param {Uint<8>} value - The value.\n */";
+ let composed = "/**\n * @description TODO\n *\n * @constraints k=?, rows=?\n *\n * @param {Uint<8>} value - The value.\n */";
+ let tag = tag("@description TODO", &[]);
+ let constraints = DocOp::InsertConstraints("@constraints k=?, rows=?".to_owned());
+
+ assert_eq!(
+ rendered(doc, 0, &[tag.clone(), constraints.clone()]),
+ composed
+ );
+ // The rule that reports first must not decide where the annotation lands.
+ assert_eq!(rendered(doc, 0, &[constraints, tag]), composed);
+ }
+
+ #[test]
+ fn two_edits_on_one_comment_merge_into_a_single_replacement() {
+ let source = "/**\n * @return nothing\n */\nexport circuit run(): [] { }\n";
+ let edits = [
+ doc_edit(
+ 0..26,
+ 0,
+ DocOp::RenameTag {
+ line: 1,
+ from: "@return".to_owned(),
+ to: "@returns".to_owned(),
+ },
+ ),
+ doc_edit(0..26, 0, tag("@description TODO", &[])),
+ ];
+
+ assert_eq!(
+ apply(source, &edits, "\n"),
+ "/**\n * @description TODO\n * @returns nothing\n */\nexport circuit run(): [] { }\n"
+ );
+ }
+
+ #[test]
+ fn an_insertion_and_a_later_comment_edit_both_land() {
+ let source =
+ "export ledger a: Uint<8>;\n/**\n * @return x\n */\nexport circuit run(): [] { }\n";
+ let edits = [
+ Edit {
+ position: Position::file_start(),
+ message: String::new(),
+ kind: EditKind::Insert {
+ offset: 0,
+ text: "/**\n * TODO\n */\n".to_owned(),
+ },
+ },
+ doc_edit(
+ 26..45,
+ 0,
+ DocOp::RenameTag {
+ line: 1,
+ from: "@return".to_owned(),
+ to: "@returns".to_owned(),
+ },
+ ),
+ ];
+
+ assert_eq!(
+ apply(source, &edits, "\n"),
+ "/**\n * TODO\n */\nexport ledger a: Uint<8>;\n/**\n * @returns x\n */\nexport circuit run(): [] { }\n"
+ );
+ }
+
+ #[test]
+ fn a_rename_leaves_the_rest_of_the_line_alone() {
+ assert_eq!(
+ rename_tag(" * @return {[]} - Empty tuple.", "@return", "@returns"),
+ Some(" * @returns {[]} - Empty tuple.".to_owned())
+ );
+ }
+
+ #[test]
+ fn a_rename_does_not_match_a_longer_tag() {
+ assert_eq!(rename_tag(" * @returns x", "@return", "@returns"), None);
+ }
+
+ #[test]
+ fn a_module_rewrite_keeps_what_follows_the_name() {
+ assert_eq!(
+ set_module_name(" * @module Stale (archived)", "Renamed"),
+ Some(" * @module Renamed (archived)".to_owned())
+ );
+ assert_eq!(
+ set_module_name(" * @module Stale", "Renamed"),
+ Some(" * @module Renamed".to_owned())
+ );
+ }
+
+ #[test]
+ fn crlf_sources_keep_their_line_ending() {
+ let source = "/**\r\n * @description Bumps.\r\n */\r\n";
+ let edits = [doc_edit(
+ 0..32,
+ 0,
+ DocOp::InsertConstraints("@constraints k=?, rows=?".to_owned()),
+ )];
+
+ assert_eq!(newline_of(source), "\r\n");
+ assert_eq!(
+ apply(source, &edits, newline_of(source)),
+ "/**\r\n * @description Bumps.\r\n *\r\n * @constraints k=?, rows=?\r\n */\r\n"
+ );
+ }
+
+ #[test]
+ fn a_file_without_a_trailing_newline_keeps_it_missing() {
+ let source = "/**\n * @description Bumps.\n */";
+ let edits = [doc_edit(0..30, 0, tag("@module M", &[]))];
+
+ let out = apply(source, &edits, "\n");
+ assert!(!out.ends_with('\n'), "{out:?}");
+ }
+}
diff --git a/crates/compact-lint/src/fix.rs b/crates/compact-lint/src/fix.rs
new file mode 100644
index 0000000..88e42bd
--- /dev/null
+++ b/crates/compact-lint/src/fix.rs
@@ -0,0 +1,377 @@
+//! The `fix` subcommand: turn the fixable issues into edits and rewrite the files.
+
+use std::path::{Path, PathBuf};
+
+use thiserror::Error;
+
+use crate::config::Config;
+use crate::doc::Tag;
+use crate::edit::{DocOp, Edit, EditKind, apply, newline_of};
+use crate::model::DeclKind;
+use crate::report::Position;
+use crate::rules::{Issue, LintError, Linter};
+use crate::target::{self, TargetError};
+
+/// The value written into an inserted constraints annotation.
+const UNMEASURED: &str = "k=?, rows=?";
+
+/// The tag whose value is the declaration's own name.
+const MODULE_TAG: &str = "@module";
+
+/// The tag that adopts untagged prose opening a doc comment.
+const DESCRIPTION_TAG: &str = "@description";
+
+#[derive(Debug, Error)]
+pub enum FixError {
+ #[error(transparent)]
+ Target(#[from] TargetError),
+ #[error(transparent)]
+ Lint(#[from] LintError),
+ #[error("cannot read {path}")]
+ Read {
+ path: PathBuf,
+ #[source]
+ source: std::io::Error,
+ },
+ #[error("cannot write {path}")]
+ Write {
+ path: PathBuf,
+ #[source]
+ source: std::io::Error,
+ },
+}
+
+/// Everything the `fix` subcommand needs, already resolved from flags.
+pub struct Options {
+ pub paths: Vec,
+ pub config_path: Option,
+ pub dry_run: bool,
+}
+
+/// The edits one file took.
+pub struct FileEdits {
+ pub path: PathBuf,
+ pub edits: Vec,
+}
+
+/// Every file the run changed, in the order they were visited.
+pub struct Outcome {
+ pub files: Vec,
+}
+
+impl Outcome {
+ #[must_use]
+ pub fn edits(&self) -> usize {
+ self.files.iter().map(|file| file.edits.len()).sum()
+ }
+
+ #[must_use]
+ pub fn summary(&self) -> String {
+ format!("{} edits in {} files", self.edits(), self.files.len())
+ }
+}
+
+/// Runs the subcommand with `cwd` as the working directory.
+/// # Errors
+/// Returns an error when the config, the file walk, the parser or a write fails.
+pub fn run(options: &Options, cwd: &Path) -> Result {
+ let target = target::resolve(&options.paths, options.config_path.as_deref(), cwd)?;
+
+ let mut linter = Linter::new()?;
+ let mut files = Vec::new();
+
+ for path in &target.files {
+ let source = std::fs::read_to_string(path).map_err(|source| FixError::Read {
+ path: path.clone(),
+ source,
+ })?;
+
+ let newline = newline_of(&source);
+ let edits: Vec = linter
+ .issues(path, &source, &target.config, false)?
+ .iter()
+ .filter_map(|issue| edit_for(issue, &target.config, newline))
+ .collect();
+
+ if edits.is_empty() {
+ continue;
+ }
+
+ if !options.dry_run {
+ write_atomically(path, &apply(&source, &edits, newline))?;
+ }
+ files.push(FileEdits {
+ path: path.clone(),
+ edits,
+ });
+ }
+
+ Ok(Outcome { files })
+}
+
+/// The repair for one issue, or `None` where the rule has no safe fix.
+///
+/// `constraints-format` and `constraints-placeholder` need a measurement a rewrite
+/// cannot invent, and `parse` and `format` are not doc-comment issues at all.
+#[must_use]
+pub fn edit_for(issue: &Issue, config: &Config, newline: &str) -> Option {
+ match issue {
+ Issue::MissingDoc {
+ position,
+ subject,
+ kind,
+ name,
+ offset,
+ indent,
+ constraints,
+ } => Some(Edit {
+ position: *position,
+ message: format!("inserted doc skeleton for {subject}"),
+ kind: EditKind::Insert {
+ offset: *offset,
+ text: skeleton(
+ *kind,
+ name.as_deref(),
+ *constraints,
+ config,
+ *indent,
+ newline,
+ ),
+ },
+ }),
+
+ Issue::MissingTag {
+ position,
+ kind,
+ tag,
+ name,
+ doc,
+ ..
+ } => Some(Edit {
+ position: *position,
+ message: format!("inserted {tag}"),
+ kind: EditKind::Doc {
+ range: doc.range.clone(),
+ indent: doc.indent,
+ op: missing_tag_op(*kind, tag, name.as_deref(), config),
+ },
+ }),
+
+ Issue::MissingConstraints {
+ position, tag, doc, ..
+ } => Some(Edit {
+ position: *position,
+ message: format!("inserted {tag} {UNMEASURED}"),
+ kind: EditKind::Doc {
+ range: doc.range.clone(),
+ indent: doc.indent,
+ op: DocOp::InsertConstraints(format!("{tag} {UNMEASURED}")),
+ },
+ }),
+
+ Issue::ForbiddenTag {
+ position,
+ tag,
+ line_offset,
+ doc,
+ } => {
+ let replacement = config.rename_of(tag)?;
+ Some(Edit {
+ position: *position,
+ message: format!("renamed {tag} to {replacement}"),
+ kind: EditKind::Doc {
+ range: doc.range.clone(),
+ indent: doc.indent,
+ op: DocOp::RenameTag {
+ line: *line_offset,
+ from: tag.to_string(),
+ to: replacement.to_string(),
+ },
+ },
+ })
+ }
+
+ Issue::ModuleName {
+ position,
+ name,
+ line_offset,
+ doc,
+ ..
+ } => Some(Edit {
+ position: *position,
+ message: format!("set {MODULE_TAG} to `{name}`"),
+ kind: EditKind::Doc {
+ range: doc.range.clone(),
+ indent: doc.indent,
+ op: DocOp::SetModuleName {
+ line: *line_offset,
+ name: name.clone(),
+ },
+ },
+ }),
+
+ Issue::Parse { .. }
+ | Issue::ConstraintsFormat { .. }
+ | Issue::ConstraintsPlaceholder { .. } => None,
+ }
+}
+
+/// The tag line lands after the tags that precede it in config order; `@description`
+/// adopts prose that opens the body.
+fn missing_tag_op(kind: DeclKind, tag: &Tag, name: Option<&str>, config: &Config) -> DocOp {
+ DocOp::InsertTag {
+ line: tag_line(tag, name, config),
+ after: config
+ .kinds
+ .get(kind)
+ .tags
+ .iter()
+ .take_while(|required| *required != tag)
+ .map(ToString::to_string)
+ .collect(),
+ adopts_prose: tag.as_str() == DESCRIPTION_TAG,
+ }
+}
+
+/// `@module` takes the declaration's own name; every other tag takes the placeholder.
+fn tag_line(tag: &Tag, name: Option<&str>, config: &Config) -> String {
+ match name {
+ Some(name) if tag.as_str() == MODULE_TAG => format!("{tag} {name}"),
+ _ => format!("{tag} {}", config.fix.placeholder),
+ }
+}
+
+/// The doc comment `fix` writes above an undocumented declaration.
+///
+/// Body lines come from `kinds..tags` in config order, then the constraints
+/// annotation where the declaration takes one.
+fn skeleton(
+ kind: DeclKind,
+ name: Option<&str>,
+ constraints: bool,
+ config: &Config,
+ indent: usize,
+ newline: &str,
+) -> String {
+ let mut body: Vec = config
+ .kinds
+ .get(kind)
+ .tags
+ .iter()
+ .map(|tag| tag_line(tag, name, config))
+ .collect();
+
+ if constraints {
+ if !body.is_empty() {
+ body.push(String::new());
+ }
+ body.push(format!("{} {UNMEASURED}", config.constraints.tag));
+ }
+ if body.is_empty() {
+ body.push(config.fix.placeholder.clone());
+ }
+
+ let margin = " ".repeat(indent);
+ let mut lines = vec!["/**".to_owned()];
+ for line in body {
+ if line.is_empty() {
+ lines.push(format!("{margin} *"));
+ } else {
+ lines.push(format!("{margin} * {line}"));
+ }
+ }
+ lines.push(format!("{margin} */"));
+
+ // The declaration follows on its own line, back at its original indentation.
+ format!("{}{newline}{margin}", lines.join(newline))
+}
+
+/// Writes through a sibling temporary file, so a failed write never truncates the source.
+fn write_atomically(path: &Path, text: &str) -> Result<(), FixError> {
+ let temporary = path.with_extension("compact.tmp");
+
+ std::fs::write(&temporary, text).map_err(|source| FixError::Write {
+ path: temporary.clone(),
+ source,
+ })?;
+ let permissions = std::fs::metadata(path)
+ .map_err(|source| FixError::Read {
+ path: path.to_owned(),
+ source,
+ })?
+ .permissions();
+ std::fs::set_permissions(&temporary, permissions).map_err(|source| FixError::Write {
+ path: temporary.clone(),
+ source,
+ })?;
+ std::fs::rename(&temporary, path).map_err(|source| FixError::Write {
+ path: path.to_owned(),
+ source,
+ })
+}
+
+/// The line `fix` prints for one edit.
+#[must_use]
+pub fn line(path: &Path, position: Position, message: &str) -> String {
+ format!(
+ "{}:{}:{}: fix: {message}",
+ path.display(),
+ position.line,
+ position.column
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::skeleton;
+ use crate::config::Config;
+ use crate::doc::Tag;
+ use crate::model::DeclKind;
+
+ fn config() -> Config {
+ let mut config = Config::default();
+ config.kinds.module.tags = vec![Tag::new("@module"), Tag::new("@description")];
+ config.kinds.circuit.tags = vec![Tag::new("@description")];
+ config
+ }
+
+ #[test]
+ fn a_module_skeleton_names_the_module() {
+ assert_eq!(
+ skeleton(DeclKind::Module, Some("Ownable"), false, &config(), 0, "\n"),
+ "/**\n * @module Ownable\n * @description TODO\n */\n"
+ );
+ }
+
+ #[test]
+ fn an_exported_circuit_skeleton_carries_the_constraints_line() {
+ assert_eq!(
+ skeleton(DeclKind::Circuit, Some("run"), true, &config(), 2, "\n"),
+ "/**\n * @description TODO\n *\n * @constraints k=?, rows=?\n */\n "
+ );
+ }
+
+ #[test]
+ fn a_pure_circuit_skeleton_stops_at_the_tag_lines() {
+ assert_eq!(
+ skeleton(DeclKind::Circuit, Some("double"), false, &config(), 2, "\n"),
+ "/**\n * @description TODO\n */\n "
+ );
+ }
+
+ #[test]
+ fn a_kind_with_no_required_tags_still_gets_a_placeholder_body() {
+ assert_eq!(
+ skeleton(DeclKind::Type, Some("RoleId"), false, &config(), 2, "\n"),
+ "/**\n * TODO\n */\n "
+ );
+ }
+
+ #[test]
+ fn a_crlf_skeleton_uses_the_file_line_ending() {
+ assert_eq!(
+ skeleton(DeclKind::Type, None, false, &config(), 0, "\r\n"),
+ "/**\r\n * TODO\r\n */\r\n"
+ );
+ }
+}
diff --git a/crates/compact-lint/src/lib.rs b/crates/compact-lint/src/lib.rs
index fdaf6e9..2eb94bb 100644
--- a/crates/compact-lint/src/lib.rs
+++ b/crates/compact-lint/src/lib.rs
@@ -2,7 +2,8 @@
//!
//! The crate parses `.compact` files with the bundled tree-sitter grammar, matches each
//! declaration against the per-kind doc-comment template in `compact-lint.toml`, and
-//! reports one [`report::Finding`] per violation.
+//! yields one [`rules::Issue`] per violation. `check` renders an issue as a
+//! [`report::Finding`]; `fix` renders the repairable ones as an [`edit::Edit`].
#![forbid(unsafe_code)]
@@ -10,7 +11,10 @@ pub mod check;
pub mod config;
pub mod discover;
pub mod doc;
+pub mod edit;
+pub mod fix;
pub mod format;
pub mod model;
pub mod report;
pub mod rules;
+pub mod target;
diff --git a/crates/compact-lint/src/main.rs b/crates/compact-lint/src/main.rs
index dc4d364..e47c785 100644
--- a/crates/compact-lint/src/main.rs
+++ b/crates/compact-lint/src/main.rs
@@ -10,10 +10,11 @@ use std::process::ExitCode;
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
-use compact_lint::check::{self, Options, Outcome};
+use compact_lint::check::{self, Outcome};
+use compact_lint::fix;
use compact_lint::format::{COMPACT_BIN_ENV, DEFAULT_COMPACT_BIN};
-/// Exit code for a run that produced findings.
+/// Exit code for a run that produced findings, or a `--dry-run` that would edit.
const EXIT_FINDINGS: u8 = 1;
/// Exit code for a usage, config, IO or parser error.
@@ -34,6 +35,8 @@ struct Cli {
enum Command {
/// Check doc comments against the per-kind templates in compact-lint.toml.
Check(CheckArgs),
+ /// Rewrite doc comments so the rules `fix` covers stop reporting.
+ Fix(FixArgs),
}
#[derive(Debug, clap::Args)]
@@ -58,6 +61,20 @@ struct CheckArgs {
compact_bin: Option,
}
+#[derive(Debug, clap::Args)]
+struct FixArgs {
+ /// Files or directories to fix; defaults to the config's include globs.
+ paths: Vec,
+
+ /// Config file to use instead of searching upward for compact-lint.toml.
+ #[arg(long, value_name = "FILE")]
+ config: Option,
+
+ /// Report the edits without writing them; exits 1 when there are any.
+ #[arg(long)]
+ dry_run: bool,
+}
+
fn main() -> ExitCode {
match run() {
Ok(code) => code,
@@ -70,10 +87,16 @@ fn main() -> ExitCode {
fn run() -> Result {
let cli = Cli::parse();
- let Command::Check(args) = cli.command;
-
let cwd = std::env::current_dir().context("reading the current directory")?;
- let options = Options {
+
+ match cli.command {
+ Command::Check(args) => run_check(args, &cwd),
+ Command::Fix(args) => run_fix(args, &cwd),
+ }
+}
+
+fn run_check(args: CheckArgs, cwd: &std::path::Path) -> Result {
+ let options = check::Options {
paths: args.paths,
config_path: args.config,
strict: args.strict,
@@ -83,8 +106,8 @@ fn run() -> Result {
.unwrap_or_else(|| OsString::from(DEFAULT_COMPACT_BIN)),
};
- let outcome = check::run(&options, &cwd).context("running the check")?;
- emit(&outcome).context("writing the report")?;
+ let outcome = check::run(&options, cwd).context("running the check")?;
+ emit_findings(&outcome).context("writing the report")?;
Ok(if outcome.findings.is_empty() {
ExitCode::SUCCESS
@@ -93,7 +116,24 @@ fn run() -> Result {
})
}
-fn emit(outcome: &Outcome) -> std::io::Result<()> {
+fn run_fix(args: FixArgs, cwd: &std::path::Path) -> Result {
+ let options = fix::Options {
+ paths: args.paths,
+ config_path: args.config,
+ dry_run: args.dry_run,
+ };
+
+ let outcome = fix::run(&options, cwd).context("running the fix")?;
+ emit_edits(&outcome).context("writing the report")?;
+
+ Ok(if options.dry_run && outcome.edits() > 0 {
+ ExitCode::from(EXIT_FINDINGS)
+ } else {
+ ExitCode::SUCCESS
+ })
+}
+
+fn emit_findings(outcome: &Outcome) -> std::io::Result<()> {
let stdout = std::io::stdout();
let mut out = stdout.lock();
for finding in &outcome.findings {
@@ -105,6 +145,24 @@ fn emit(outcome: &Outcome) -> std::io::Result<()> {
Ok(())
}
+fn emit_edits(outcome: &fix::Outcome) -> std::io::Result<()> {
+ let stdout = std::io::stdout();
+ let mut out = stdout.lock();
+ for file in &outcome.files {
+ for edit in &file.edits {
+ writeln!(
+ out,
+ "{}",
+ fix::line(&file.path, edit.position, &edit.message)
+ )?;
+ }
+ }
+ out.flush()?;
+
+ eprintln!("{}", outcome.summary());
+ Ok(())
+}
+
/// Prints the error and every source under it, one cause per line.
fn report_error(error: &anyhow::Error) {
eprintln!("compact-lint: {error}");
diff --git a/crates/compact-lint/src/model.rs b/crates/compact-lint/src/model.rs
index 968eee8..5634f9c 100644
--- a/crates/compact-lint/src/model.rs
+++ b/crates/compact-lint/src/model.rs
@@ -1,6 +1,7 @@
//! Declarations the linter checks, lifted out of the tree-sitter tree.
use std::fmt;
+use std::ops::Range;
use tree_sitter::Node;
@@ -75,6 +76,8 @@ pub struct Declaration {
pub exported: bool,
pub pure: bool,
pub position: Position,
+ /// Byte offset of the declaration's first token, where `fix` inserts a skeleton.
+ pub offset: usize,
/// The doc comment immediately preceding the declaration, if one attaches.
pub doc: Option,
}
@@ -86,6 +89,8 @@ pub struct AttachedDoc {
/// 0-based row of the `/**` line, for turning a tag's offset into a position.
pub start_row: usize,
pub start_column: usize,
+ /// Byte range of the `/** … */` node, the span `fix` rewrites.
+ pub range: Range,
}
impl AttachedDoc {
@@ -152,6 +157,7 @@ fn declaration(kind: DeclKind, node: Node<'_>, source: &str) -> Declaration {
exported,
pure,
position: Position::from_zero_based(start.row, start.column),
+ offset: node.start_byte(),
doc: attached_doc(node, source),
}
}
@@ -169,6 +175,7 @@ fn attached_doc(node: Node<'_>, source: &str) -> Option {
comment: DocComment::parse(text(previous, source)?),
start_row: start.row,
start_column: start.column,
+ range: previous.start_byte()..previous.end_byte(),
})
}
diff --git a/crates/compact-lint/src/rules.rs b/crates/compact-lint/src/rules.rs
index 9bc0bf7..eb723dd 100644
--- a/crates/compact-lint/src/rules.rs
+++ b/crates/compact-lint/src/rules.rs
@@ -1,5 +1,9 @@
//! The doc-comment rules, run over one parsed file at a time.
+//!
+//! Detection yields [`Issue`]s. `check` renders them as [`Finding`]s and `fix` renders
+//! the fixable ones as edits, so both subcommands read the same detection pass.
+use std::ops::Range;
use std::path::{Path, PathBuf};
use thiserror::Error;
@@ -18,6 +22,142 @@ pub enum LintError {
NoTree(PathBuf),
}
+/// Where a doc comment sits in the file, for the rules that rewrite one.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct DocRef {
+ /// Byte range of the `/** … */` node.
+ pub range: Range,
+ /// Column the `/**` starts on, reused as the rewritten comment's indentation.
+ pub indent: usize,
+}
+
+/// One rule violation, carrying what `check` prints and what `fix` needs to repair it.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum Issue {
+ Parse {
+ position: Position,
+ node_kind: String,
+ missing: bool,
+ },
+ MissingDoc {
+ position: Position,
+ subject: String,
+ kind: DeclKind,
+ name: Option,
+ /// Byte offset of the declaration's first token.
+ offset: usize,
+ /// The declaration's start column, reused as the skeleton's indentation.
+ indent: usize,
+ /// The declaration also carries the constraints annotation.
+ constraints: bool,
+ },
+ MissingTag {
+ position: Position,
+ subject: String,
+ kind: DeclKind,
+ tag: Tag,
+ /// The declaration's name, which `@module` takes as its value.
+ name: Option,
+ doc: DocRef,
+ },
+ ForbiddenTag {
+ position: Position,
+ tag: Tag,
+ /// 0-based line index of the tag inside the doc comment.
+ line_offset: usize,
+ doc: DocRef,
+ },
+ ModuleName {
+ position: Position,
+ documented: String,
+ name: String,
+ /// 0-based line index of `@module` inside the doc comment.
+ line_offset: usize,
+ doc: DocRef,
+ },
+ MissingConstraints {
+ position: Position,
+ subject: String,
+ tag: Tag,
+ doc: DocRef,
+ },
+ ConstraintsFormat {
+ position: Position,
+ tag: Tag,
+ value: String,
+ },
+ ConstraintsPlaceholder {
+ position: Position,
+ tag: Tag,
+ value: String,
+ },
+}
+
+impl Issue {
+ #[must_use]
+ pub const fn position(&self) -> Position {
+ match self {
+ Self::Parse { position, .. }
+ | Self::MissingDoc { position, .. }
+ | Self::MissingTag { position, .. }
+ | Self::ForbiddenTag { position, .. }
+ | Self::ModuleName { position, .. }
+ | Self::MissingConstraints { position, .. }
+ | Self::ConstraintsFormat { position, .. }
+ | Self::ConstraintsPlaceholder { position, .. } => *position,
+ }
+ }
+
+ #[must_use]
+ pub const fn rule(&self) -> RuleId {
+ match self {
+ Self::Parse { .. } => RuleId::PARSE,
+ Self::MissingDoc { .. } => RuleId::MISSING_DOC,
+ Self::MissingTag { .. } => RuleId::MISSING_TAG,
+ Self::ForbiddenTag { .. } => RuleId::FORBIDDEN_TAG,
+ Self::ModuleName { .. } => RuleId::MODULE_NAME,
+ Self::MissingConstraints { .. } => RuleId::MISSING_CONSTRAINTS,
+ Self::ConstraintsFormat { .. } => RuleId::CONSTRAINTS_FORMAT,
+ Self::ConstraintsPlaceholder { .. } => RuleId::CONSTRAINTS_PLACEHOLDER,
+ }
+ }
+
+ #[must_use]
+ pub fn message(&self) -> String {
+ match self {
+ Self::Parse {
+ node_kind, missing, ..
+ } => {
+ if *missing {
+ format!("parse error: missing {node_kind}")
+ } else {
+ format!("parse error: unexpected {node_kind}")
+ }
+ }
+ Self::MissingDoc { subject, .. } => format!("{subject} has no doc comment"),
+ Self::MissingTag { subject, tag, .. }
+ | Self::MissingConstraints { subject, tag, .. } => {
+ format!("{subject} doc comment has no {tag}")
+ }
+ Self::ForbiddenTag { tag, .. } => format!("forbidden tag {tag}"),
+ Self::ModuleName {
+ documented, name, ..
+ } => format!("@module names `{documented}`, but the module is `{name}`"),
+ Self::ConstraintsFormat { tag, value, .. } => {
+ format!("{tag} value `{value}` is not `k=, rows=`")
+ }
+ Self::ConstraintsPlaceholder { tag, value, .. } => {
+ format!("{tag} value `{value}` still holds a placeholder")
+ }
+ }
+ }
+
+ #[must_use]
+ pub fn finding(&self, display: &Path) -> Finding {
+ Finding::new(display, self.position(), self.rule(), self.message())
+ }
+}
+
/// A reusable parser plus the rules that run over its trees.
pub struct Linter {
parser: Parser,
@@ -34,19 +174,19 @@ impl Linter {
Ok(Self { parser })
}
- /// Checks one file. `display` is the path printed in findings.
+ /// Runs every rule over one file, in source order.
///
- /// A tree holding an `ERROR` or `MISSING` node yields a single `parse` finding and
- /// no doc findings, because declarations around the defect are not trustworthy.
+ /// A tree holding an `ERROR` or `MISSING` node yields a single `parse` issue and no
+ /// doc issues, because declarations around the defect are not trustworthy.
/// # Errors
/// Returns an error when tree-sitter produces no tree for `source`.
- pub fn check(
+ pub fn issues(
&mut self,
display: &Path,
source: &str,
config: &Config,
strict: bool,
- ) -> Result, LintError> {
+ ) -> Result, LintError> {
let tree = self
.parser
.parse(source, None)
@@ -54,42 +194,42 @@ impl Linter {
let root = tree.root_node();
if let Some(defect) = first_defect(root) {
- return Ok(vec![defect.finding(display)]);
+ return Ok(vec![defect]);
}
- let mut findings = Vec::new();
+ let mut issues = Vec::new();
for declaration in declarations(root, source) {
- check_declaration(&declaration, display, config, strict, &mut findings);
+ check_declaration(&declaration, config, strict, &mut issues);
}
- findings.sort_by(|left, right| left.sort_key().cmp(&right.sort_key()));
- Ok(findings)
+ issues.sort_by_key(|issue| (issue.position(), issue.rule()));
+ Ok(issues)
}
-}
-
-struct Defect {
- position: Position,
- kind: String,
- missing: bool,
-}
-impl Defect {
- fn finding(&self, display: &Path) -> Finding {
- let message = if self.missing {
- format!("parse error: missing {}", self.kind)
- } else {
- format!("parse error: unexpected {}", self.kind)
- };
- Finding::new(display, self.position, RuleId::PARSE, message)
+ /// Checks one file. `display` is the path printed in findings.
+ /// # Errors
+ /// Returns an error when tree-sitter produces no tree for `source`.
+ pub fn check(
+ &mut self,
+ display: &Path,
+ source: &str,
+ config: &Config,
+ strict: bool,
+ ) -> Result, LintError> {
+ Ok(self
+ .issues(display, source, config, strict)?
+ .iter()
+ .map(|issue| issue.finding(display))
+ .collect())
}
}
/// The first `ERROR` or `MISSING` node in document order.
-fn first_defect(node: Node<'_>) -> Option {
+fn first_defect(node: Node<'_>) -> Option {
if node.is_error() || node.is_missing() {
let start = node.start_position();
- return Some(Defect {
+ return Some(Issue::Parse {
position: Position::from_zero_based(start.row, start.column),
- kind: node.kind().to_owned(),
+ node_kind: node.kind().to_owned(),
missing: node.is_missing(),
});
}
@@ -104,10 +244,9 @@ fn first_defect(node: Node<'_>) -> Option {
fn check_declaration(
declaration: &Declaration,
- display: &Path,
config: &Config,
strict: bool,
- findings: &mut Vec,
+ issues: &mut Vec,
) {
let kind_config = config.kinds.get(declaration.kind);
let requires_docs = match kind_config.docs {
@@ -118,32 +257,40 @@ fn check_declaration(
let Some(attached) = declaration.doc.as_ref() else {
if requires_docs {
- findings.push(Finding::new(
- display,
- declaration.position,
- RuleId::MISSING_DOC,
- format!("{} has no doc comment", subject(declaration)),
- ));
+ issues.push(Issue::MissingDoc {
+ position: declaration.position,
+ subject: subject(declaration),
+ kind: declaration.kind,
+ name: declaration.name.clone(),
+ offset: declaration.offset,
+ indent: declaration.position.column - 1,
+ constraints: takes_constraints(declaration),
+ });
}
return;
};
- // A forbidden tag is a finding wherever it appears, documented or not.
+ let doc = DocRef {
+ range: attached.range.clone(),
+ indent: attached.start_column,
+ };
+
+ // A forbidden tag is an issue wherever it appears, documented or not.
for forbidden in &config.tags.forbid {
for occurrence in attached.comment.tags() {
if &occurrence.tag == forbidden {
- findings.push(Finding::new(
- display,
- attached.tag_position(occurrence),
- RuleId::FORBIDDEN_TAG,
- format!("forbidden tag {forbidden}"),
- ));
+ issues.push(Issue::ForbiddenTag {
+ position: attached.tag_position(occurrence),
+ tag: forbidden.clone(),
+ line_offset: occurrence.line_offset,
+ doc: doc.clone(),
+ });
}
}
}
if declaration.kind == DeclKind::Module {
- check_module_name(declaration, display, findings);
+ check_module_name(declaration, &doc, issues);
}
if !requires_docs {
@@ -152,20 +299,22 @@ fn check_declaration(
for required in &kind_config.tags {
if !attached.comment.has(required) {
- findings.push(Finding::new(
- display,
- declaration.position,
- RuleId::MISSING_TAG,
- format!("{} doc comment has no {required}", subject(declaration)),
- ));
+ issues.push(Issue::MissingTag {
+ position: declaration.position,
+ subject: subject(declaration),
+ kind: declaration.kind,
+ tag: required.clone(),
+ name: declaration.name.clone(),
+ doc: doc.clone(),
+ });
}
}
- check_constraints(declaration, display, config, strict, findings);
+ check_constraints(declaration, &doc, config, strict, issues);
}
/// `@module ` must name the module it documents.
-fn check_module_name(declaration: &Declaration, display: &Path, findings: &mut Vec) {
+fn check_module_name(declaration: &Declaration, doc: &DocRef, issues: &mut Vec) {
let tag = Tag::new("@module");
let (Some(attached), Some(name)) = (declaration.doc.as_ref(), declaration.name.as_deref())
else {
@@ -177,12 +326,13 @@ fn check_module_name(declaration: &Declaration, display: &Path, findings: &mut V
let documented = module_name(&occurrence.value);
if documented != name {
- findings.push(Finding::new(
- display,
- attached.tag_position(occurrence),
- RuleId::MODULE_NAME,
- format!("@module names `{documented}`, but the module is `{name}`"),
- ));
+ issues.push(Issue::ModuleName {
+ position: attached.tag_position(occurrence),
+ documented: documented.to_owned(),
+ name: name.to_owned(),
+ line_offset: occurrence.line_offset,
+ doc: doc.clone(),
+ });
}
}
@@ -194,14 +344,18 @@ fn module_name(value: &str) -> &str {
}
/// Exported non-pure circuits carry the constraints annotation.
+const fn takes_constraints(declaration: &Declaration) -> bool {
+ matches!(declaration.kind, DeclKind::Circuit) && declaration.exported && !declaration.pure
+}
+
fn check_constraints(
declaration: &Declaration,
- display: &Path,
+ doc: &DocRef,
config: &Config,
strict: bool,
- findings: &mut Vec,
+ issues: &mut Vec,
) {
- if declaration.kind != DeclKind::Circuit || !declaration.exported || declaration.pure {
+ if !takes_constraints(declaration) {
return;
}
let Some(attached) = declaration.doc.as_ref() else {
@@ -210,12 +364,12 @@ fn check_constraints(
let tag = &config.constraints.tag;
let Some(occurrence) = attached.comment.first(tag) else {
- findings.push(Finding::new(
- display,
- declaration.position,
- RuleId::MISSING_CONSTRAINTS,
- format!("{} doc comment has no {tag}", subject(declaration)),
- ));
+ issues.push(Issue::MissingConstraints {
+ position: declaration.position,
+ subject: subject(declaration),
+ tag: tag.clone(),
+ doc: doc.clone(),
+ });
return;
};
@@ -224,22 +378,20 @@ fn check_constraints(
let position = attached.tag_position(occurrence);
let Some(placeholders) = parse_constraints(value) else {
- findings.push(Finding::new(
- display,
+ issues.push(Issue::ConstraintsFormat {
position,
- RuleId::CONSTRAINTS_FORMAT,
- format!("{tag} value `{value}` is not `k=, rows=`"),
- ));
+ tag: tag.clone(),
+ value: value.to_owned(),
+ });
return;
};
if strict && placeholders {
- findings.push(Finding::new(
- display,
+ issues.push(Issue::ConstraintsPlaceholder {
position,
- RuleId::CONSTRAINTS_PLACEHOLDER,
- format!("{tag} value `{value}` still holds a placeholder"),
- ));
+ tag: tag.clone(),
+ value: value.to_owned(),
+ });
}
}
diff --git a/crates/compact-lint/src/target.rs b/crates/compact-lint/src/target.rs
new file mode 100644
index 0000000..aaa5e16
--- /dev/null
+++ b/crates/compact-lint/src/target.rs
@@ -0,0 +1,65 @@
+//! What a subcommand runs over: the resolved config plus the file list.
+
+use std::path::{Path, PathBuf};
+
+use thiserror::Error;
+
+use crate::config::{Config, ConfigError, discover as discover_config};
+use crate::discover::{self, DiscoverError};
+
+#[derive(Debug, Error)]
+pub enum TargetError {
+ #[error(transparent)]
+ Config(#[from] ConfigError),
+ #[error(transparent)]
+ Discover(#[from] DiscoverError),
+}
+
+/// The config and the files it selects, shared by `check` and `fix`.
+pub struct Target {
+ pub config: Config,
+ pub files: Vec,
+}
+
+/// Resolves the config and the file list for one run.
+///
+/// An explicit `config_path` is a shareable preset, so glob discovery stays anchored at
+/// `cwd`; a config found by walking upward anchors discovery at its own directory.
+/// # Errors
+/// Returns an error when the config is unreadable or a path cannot be walked.
+pub fn resolve(
+ paths: &[PathBuf],
+ config_path: Option<&Path>,
+ cwd: &Path,
+) -> Result {
+ let (config, source, base) = match config_path {
+ Some(path) => (Config::load(path)?, path.to_owned(), cwd.to_owned()),
+ None => match discover_config(cwd) {
+ Some(path) => {
+ let base = path.parent().unwrap_or(cwd).to_owned();
+ (Config::load(&path)?, path, base)
+ }
+ None => (
+ Config::default(),
+ PathBuf::from(""),
+ cwd.to_owned(),
+ ),
+ },
+ };
+
+ let exclude = config.exclude_set(&source)?;
+ let files = if paths.is_empty() {
+ let include = config.include_set(&source)?;
+ discover::from_globs(&base, &include, &exclude)?
+ } else {
+ discover::from_paths(paths, &base, &exclude)?
+ };
+
+ let files = files.iter().map(|path| display_path(path, cwd)).collect();
+ Ok(Target { config, files })
+}
+
+/// Paths under the working directory print relative to it; anything else prints as is.
+fn display_path(path: &Path, cwd: &Path) -> PathBuf {
+ path.strip_prefix(cwd).unwrap_or(path).to_owned()
+}
diff --git a/crates/compact-lint/tests/fix.rs b/crates/compact-lint/tests/fix.rs
new file mode 100644
index 0000000..fae2b52
--- /dev/null
+++ b/crates/compact-lint/tests/fix.rs
@@ -0,0 +1,223 @@
+//! `compact-lint fix` end to end, one fixture case per rule.
+//!
+//! A case holds `compact-lint.toml`, `before/`, `after/` and `expected.txt`. The run
+//! works on a copy of `before/`, so the fixtures themselves are never rewritten.
+
+use std::collections::BTreeMap;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+use assert_cmd::prelude::*;
+use tempfile::TempDir;
+
+/// Exit code for a `--dry-run` that would edit.
+const EXIT_EDITS: i32 = 1;
+
+fn case(name: &str) -> PathBuf {
+ PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+ .join("tests/fixtures")
+ .join(format!("fix-{name}"))
+ .canonicalize()
+ .expect("the fixture case exists")
+}
+
+fn binary() -> Command {
+ Command::cargo_bin("compact-lint").expect("the binary is built alongside this test")
+}
+
+/// Copies the case's config and `before/` tree into a fresh temporary directory.
+fn work(name: &str) -> TempDir {
+ let directory = TempDir::new().expect("a temporary directory is available");
+ let root = case(name);
+
+ std::fs::copy(
+ root.join("compact-lint.toml"),
+ directory.path().join("compact-lint.toml"),
+ )
+ .expect("the case has a config");
+ copy_tree(&root.join("before"), directory.path());
+
+ directory
+}
+
+fn copy_tree(from: &Path, to: &Path) {
+ for entry in std::fs::read_dir(from).expect("the tree is readable") {
+ let entry = entry.expect("the entry is readable");
+ let target = to.join(entry.file_name());
+ if entry.path().is_dir() {
+ std::fs::create_dir_all(&target).expect("the target is writable");
+ copy_tree(&entry.path(), &target);
+ } else {
+ std::fs::copy(entry.path(), &target).expect("the file is copyable");
+ }
+ }
+}
+
+/// Every `.compact` file under `root`, keyed by its path relative to it.
+fn sources(root: &Path) -> BTreeMap> {
+ let mut found = BTreeMap::new();
+ collect(root, root, &mut found);
+ found
+}
+
+fn collect(root: &Path, directory: &Path, out: &mut BTreeMap>) {
+ for entry in std::fs::read_dir(directory).expect("the tree is readable") {
+ let entry = entry.expect("the entry is readable");
+ let path = entry.path();
+ if path.is_dir() {
+ collect(root, &path, out);
+ } else if path
+ .extension()
+ .is_some_and(|extension| extension == "compact")
+ {
+ let relative = path.strip_prefix(root).expect("the path is under the root");
+ out.insert(
+ relative.to_owned(),
+ std::fs::read(&path).expect("the source is readable"),
+ );
+ }
+ }
+}
+
+fn expected(name: &str) -> String {
+ std::fs::read_to_string(case(name).join("expected.txt")).expect("the case has an expectation")
+}
+
+/// Runs a subcommand inside `directory` and returns its exit code and stdout.
+fn run(directory: &Path, args: &[&str]) -> (i32, String) {
+ let output = binary()
+ .current_dir(directory)
+ .args(args)
+ .output()
+ .expect("the binary ran");
+
+ (
+ output.status.code().expect("the binary was not signalled"),
+ String::from_utf8(output.stdout).expect("the report is UTF-8"),
+ )
+}
+
+/// The whole contract of a fixable case: it fixes, it lands, it settles, it previews.
+fn assert_case(name: &str) {
+ let directory = work(name);
+ let root = directory.path();
+
+ let (code, stdout) = run(root, &["fix"]);
+ assert_eq!(stdout, expected(name), "stdout for case {name}");
+ assert_eq!(code, 0, "exit code for case {name}");
+ assert_eq!(
+ sources(root),
+ sources(&case(name).join("after")),
+ "rewritten sources for case {name}"
+ );
+
+ let (code, stdout) = run(root, &["check", "--no-format"]);
+ assert_eq!(stdout, "", "check is clean after fixing case {name}");
+ assert_eq!(code, 0, "check exit code after fixing case {name}");
+
+ let (code, stdout) = run(root, &["fix"]);
+ assert_eq!(stdout, "", "a second fix on case {name} edits nothing");
+ assert_eq!(code, 0, "second fix exit code for case {name}");
+
+ let preview = work(name);
+ let before = sources(preview.path());
+ let (code, stdout) = run(preview.path(), &["fix", "--dry-run"]);
+ assert_eq!(stdout, expected(name), "dry-run stdout for case {name}");
+ assert_eq!(code, EXIT_EDITS, "dry-run exit code for case {name}");
+ assert_eq!(
+ sources(preview.path()),
+ before,
+ "dry-run wrote to case {name}"
+ );
+}
+
+#[test]
+fn undocumented_declarations_of_every_kind_take_a_skeleton() {
+ assert_case("skeleton");
+}
+
+#[test]
+fn a_required_tag_goes_into_the_comment_that_lacks_it() {
+ assert_case("missing-tag");
+}
+
+#[test]
+fn the_constraints_annotation_follows_the_description_block() {
+ assert_case("constraints");
+}
+
+#[test]
+fn a_forbidden_tag_with_a_replacement_is_renamed_in_place() {
+ assert_case("rename");
+}
+
+#[test]
+fn a_stale_module_tag_takes_the_module_name() {
+ assert_case("module-name");
+}
+
+#[test]
+fn a_crlf_source_keeps_its_line_endings() {
+ assert_case("crlf");
+}
+
+#[test]
+fn nothing_fixable_writes_nothing_and_exits_zero() {
+ let directory = work("unfixable");
+ let root = directory.path();
+ let before = sources(root);
+
+ let (code, stdout) = run(root, &["fix"]);
+ assert_eq!(stdout, "");
+ assert_eq!(code, 0);
+ assert_eq!(sources(root), before);
+
+ let (code, stdout) = run(root, &["fix", "--dry-run"]);
+ assert_eq!(stdout, "");
+ assert_eq!(code, 0);
+}
+
+#[test]
+fn the_summary_goes_to_stderr_and_the_edits_to_stdout() {
+ let directory = work("rename");
+ let output = binary()
+ .current_dir(directory.path())
+ .arg("fix")
+ .output()
+ .expect("the binary ran");
+
+ let stderr = String::from_utf8(output.stderr).expect("the summary is UTF-8");
+ assert_eq!(stderr.trim_end(), "2 edits in 1 files");
+ assert!(
+ String::from_utf8_lossy(&output.stdout).contains(": fix: "),
+ "the edits belong on stdout"
+ );
+}
+
+#[test]
+fn an_explicit_config_replaces_the_one_beside_the_sources() {
+ let directory = work("rename");
+ std::fs::remove_file(directory.path().join("compact-lint.toml"))
+ .expect("the copied config is removable");
+
+ let config = case("rename").join("compact-lint.toml");
+ let output = binary()
+ .current_dir(directory.path())
+ .args(["fix", "--config"])
+ .arg(&config)
+ .output()
+ .expect("the binary ran");
+
+ assert_eq!(
+ String::from_utf8(output.stdout).expect("the report is UTF-8"),
+ expected("rename")
+ );
+}
+
+#[test]
+fn a_missing_path_exits_two() {
+ let directory = work("rename");
+ let (code, _) = run(directory.path(), &["fix", "Nope.compact"]);
+
+ assert_eq!(code, 2);
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-constraints/after/Unmeasured.compact b/crates/compact-lint/tests/fixtures/fix-constraints/after/Unmeasured.compact
new file mode 100644
index 0000000..6c55524
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-constraints/after/Unmeasured.compact
@@ -0,0 +1,37 @@
+/**
+ * @module Unmeasured
+ * @description A module whose exported circuits are unmeasured.
+ */
+export module Unmeasured {
+ /**
+ * @description Bumps the counter.
+ *
+ * @constraints k=?, rows=?
+ */
+ export circuit bump(): [] { }
+
+ /**
+ * @description Sends a coin
+ * to the recipient named below.
+ *
+ * @constraints k=?, rows=?
+ *
+ * @param {Bytes<32>} to - The recipient.
+ * @returns {[]} Empty tuple.
+ */
+ export circuit send(to: Bytes<32>): [] { }
+
+ /**
+ * @constraints k=?, rows=?
+ *
+ * @notice The annotation goes to the top, since there is no description.
+ */
+ export circuit run(): [] { }
+
+ /**
+ * @description Pure circuits are never annotated.
+ */
+ export pure circuit double(value: Uint<8>): Uint<16> {
+ return value as Uint<16> * 2;
+ }
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-constraints/before/Unmeasured.compact b/crates/compact-lint/tests/fixtures/fix-constraints/before/Unmeasured.compact
new file mode 100644
index 0000000..ea9ffde
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-constraints/before/Unmeasured.compact
@@ -0,0 +1,31 @@
+/**
+ * @module Unmeasured
+ * @description A module whose exported circuits are unmeasured.
+ */
+export module Unmeasured {
+ /**
+ * @description Bumps the counter.
+ */
+ export circuit bump(): [] { }
+
+ /**
+ * @description Sends a coin
+ * to the recipient named below.
+ *
+ * @param {Bytes<32>} to - The recipient.
+ * @returns {[]} Empty tuple.
+ */
+ export circuit send(to: Bytes<32>): [] { }
+
+ /**
+ * @notice The annotation goes to the top, since there is no description.
+ */
+ export circuit run(): [] { }
+
+ /**
+ * @description Pure circuits are never annotated.
+ */
+ export pure circuit double(value: Uint<8>): Uint<16> {
+ return value as Uint<16> * 2;
+ }
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-constraints/compact-lint.toml b/crates/compact-lint/tests/fixtures/fix-constraints/compact-lint.toml
new file mode 100644
index 0000000..832f4ee
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-constraints/compact-lint.toml
@@ -0,0 +1,13 @@
+version = 1
+include = ["**/*.compact"]
+
+[constraints]
+tag = "@constraints"
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.circuit]
+
+[kinds.constructor]
+docs = "none"
diff --git a/crates/compact-lint/tests/fixtures/fix-constraints/expected.txt b/crates/compact-lint/tests/fixtures/fix-constraints/expected.txt
new file mode 100644
index 0000000..9b49823
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-constraints/expected.txt
@@ -0,0 +1,3 @@
+Unmeasured.compact:9:3: fix: inserted @constraints k=?, rows=?
+Unmeasured.compact:18:3: fix: inserted @constraints k=?, rows=?
+Unmeasured.compact:23:3: fix: inserted @constraints k=?, rows=?
diff --git a/crates/compact-lint/tests/fixtures/fix-crlf/after/Windows.compact b/crates/compact-lint/tests/fixtures/fix-crlf/after/Windows.compact
new file mode 100644
index 0000000..d1f3118
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-crlf/after/Windows.compact
@@ -0,0 +1,19 @@
+/**
+ * @module Windows
+ * @description A module written with Windows line endings.
+ */
+export module Windows {
+ /**
+ * @description TODO
+ */
+ export ledger _count: Counter;
+
+ /**
+ * @description Bumps the counter.
+ *
+ * @constraints k=?, rows=?
+ *
+ * @returns {[]} - Empty tuple.
+ */
+ export circuit bump(): [] { }
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-crlf/before/Windows.compact b/crates/compact-lint/tests/fixtures/fix-crlf/before/Windows.compact
new file mode 100644
index 0000000..2ba9860
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-crlf/before/Windows.compact
@@ -0,0 +1,13 @@
+/**
+ * @module Windows
+ * @description A module written with Windows line endings.
+ */
+export module Windows {
+ export ledger _count: Counter;
+
+ /**
+ * @description Bumps the counter.
+ * @return {[]} - Empty tuple.
+ */
+ export circuit bump(): [] { }
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-crlf/compact-lint.toml b/crates/compact-lint/tests/fixtures/fix-crlf/compact-lint.toml
new file mode 100644
index 0000000..5e3068c
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-crlf/compact-lint.toml
@@ -0,0 +1,18 @@
+version = 1
+include = ["**/*.compact"]
+
+[tags]
+forbid = ["@return"]
+rename = { "@return" = "@returns" }
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.circuit]
+tags = ["@description"]
+
+[kinds.ledger]
+tags = ["@description"]
+
+[kinds.constructor]
+docs = "none"
diff --git a/crates/compact-lint/tests/fixtures/fix-crlf/expected.txt b/crates/compact-lint/tests/fixtures/fix-crlf/expected.txt
new file mode 100644
index 0000000..994f2b4
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-crlf/expected.txt
@@ -0,0 +1,3 @@
+Windows.compact:6:3: fix: inserted doc skeleton for ledger `_count`
+Windows.compact:10:6: fix: renamed @return to @returns
+Windows.compact:12:3: fix: inserted @constraints k=?, rows=?
diff --git a/crates/compact-lint/tests/fixtures/fix-missing-tag/after/Untagged.compact b/crates/compact-lint/tests/fixtures/fix-missing-tag/after/Untagged.compact
new file mode 100644
index 0000000..5f112de
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-missing-tag/after/Untagged.compact
@@ -0,0 +1,25 @@
+/**
+ * @module Untagged
+ * @description TODO
+ */
+export module Untagged {
+ /**
+ * @description Approved accounts. Absent means not allowed.
+ */
+ export ledger _allowed: Set>;
+
+ /**
+ * @description The circuit has no description either.
+ * @constraints k=6, rows=28
+ */
+ export circuit run(): [] { }
+
+ /**
+ * @description TODO
+ *
+ * @constraints k=?, rows=?
+ *
+ * @param {Uint<8>} value - The value to store.
+ */
+ export circuit store(value: Uint<8>): [] { }
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-missing-tag/before/Untagged.compact b/crates/compact-lint/tests/fixtures/fix-missing-tag/before/Untagged.compact
new file mode 100644
index 0000000..49ef079
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-missing-tag/before/Untagged.compact
@@ -0,0 +1,18 @@
+/**
+ * @module Untagged
+ */
+export module Untagged {
+ /** Approved accounts. Absent means not allowed. */
+ export ledger _allowed: Set>;
+
+ /**
+ * The circuit has no description either.
+ * @constraints k=6, rows=28
+ */
+ export circuit run(): [] { }
+
+ /**
+ * @param {Uint<8>} value - The value to store.
+ */
+ export circuit store(value: Uint<8>): [] { }
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-missing-tag/compact-lint.toml b/crates/compact-lint/tests/fixtures/fix-missing-tag/compact-lint.toml
new file mode 100644
index 0000000..3af7b21
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-missing-tag/compact-lint.toml
@@ -0,0 +1,14 @@
+version = 1
+include = ["**/*.compact"]
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.circuit]
+tags = ["@description"]
+
+[kinds.ledger]
+tags = ["@description"]
+
+[kinds.constructor]
+docs = "none"
diff --git a/crates/compact-lint/tests/fixtures/fix-missing-tag/expected.txt b/crates/compact-lint/tests/fixtures/fix-missing-tag/expected.txt
new file mode 100644
index 0000000..a33b4e2
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-missing-tag/expected.txt
@@ -0,0 +1,5 @@
+Untagged.compact:4:1: fix: inserted @description
+Untagged.compact:6:3: fix: inserted @description
+Untagged.compact:12:3: fix: inserted @description
+Untagged.compact:17:3: fix: inserted @constraints k=?, rows=?
+Untagged.compact:17:3: fix: inserted @description
diff --git a/crates/compact-lint/tests/fixtures/fix-module-name/after/Renamed.compact b/crates/compact-lint/tests/fixtures/fix-module-name/after/Renamed.compact
new file mode 100644
index 0000000..4e467b1
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-module-name/after/Renamed.compact
@@ -0,0 +1,12 @@
+/**
+ * @module Renamed (archived)
+ * @description The tag still names the module's old name.
+ */
+export module Renamed {
+ /**
+ * @module Nested
+ * @description The inner tag names nothing that exists.
+ */
+ export module Nested {
+ }
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-module-name/before/Renamed.compact b/crates/compact-lint/tests/fixtures/fix-module-name/before/Renamed.compact
new file mode 100644
index 0000000..2ac3362
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-module-name/before/Renamed.compact
@@ -0,0 +1,12 @@
+/**
+ * @module Stale (archived)
+ * @description The tag still names the module's old name.
+ */
+export module Renamed {
+ /**
+ * @module Inner.
+ * @description The inner tag names nothing that exists.
+ */
+ export module Nested {
+ }
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-module-name/compact-lint.toml b/crates/compact-lint/tests/fixtures/fix-module-name/compact-lint.toml
new file mode 100644
index 0000000..d404f25
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-module-name/compact-lint.toml
@@ -0,0 +1,8 @@
+version = 1
+include = ["**/*.compact"]
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.constructor]
+docs = "none"
diff --git a/crates/compact-lint/tests/fixtures/fix-module-name/expected.txt b/crates/compact-lint/tests/fixtures/fix-module-name/expected.txt
new file mode 100644
index 0000000..5e9811a
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-module-name/expected.txt
@@ -0,0 +1,2 @@
+Renamed.compact:2:4: fix: set @module to `Renamed`
+Renamed.compact:7:6: fix: set @module to `Nested`
diff --git a/crates/compact-lint/tests/fixtures/fix-rename/after/Returns.compact b/crates/compact-lint/tests/fixtures/fix-rename/after/Returns.compact
new file mode 100644
index 0000000..21cbe5d
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-rename/after/Returns.compact
@@ -0,0 +1,19 @@
+/**
+ * @module Returns
+ * @description A module that still uses the old return tag.
+ */
+export module Returns {
+ /**
+ * @description Does nothing.
+ * @constraints k=6, rows=28
+ * @returns {[]} - Empty tuple.
+ */
+ export circuit run(): [] { }
+
+ /**
+ * @description Not exported, so its docs are optional, but the tag is not.
+ * @returns {[]} - Empty tuple.
+ * @returns {[]} - The new spelling is left alone.
+ */
+ circuit helper(): [] { }
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-rename/before/Returns.compact b/crates/compact-lint/tests/fixtures/fix-rename/before/Returns.compact
new file mode 100644
index 0000000..8c8fc7a
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-rename/before/Returns.compact
@@ -0,0 +1,19 @@
+/**
+ * @module Returns
+ * @description A module that still uses the old return tag.
+ */
+export module Returns {
+ /**
+ * @description Does nothing.
+ * @constraints k=6, rows=28
+ * @return {[]} - Empty tuple.
+ */
+ export circuit run(): [] { }
+
+ /**
+ * @description Not exported, so its docs are optional, but the tag is not.
+ * @return {[]} - Empty tuple.
+ * @returns {[]} - The new spelling is left alone.
+ */
+ circuit helper(): [] { }
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-rename/compact-lint.toml b/crates/compact-lint/tests/fixtures/fix-rename/compact-lint.toml
new file mode 100644
index 0000000..ad6ecc5
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-rename/compact-lint.toml
@@ -0,0 +1,15 @@
+version = 1
+include = ["**/*.compact"]
+
+[tags]
+forbid = ["@return"]
+rename = { "@return" = "@returns" }
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.circuit]
+tags = ["@description"]
+
+[kinds.constructor]
+docs = "none"
diff --git a/crates/compact-lint/tests/fixtures/fix-rename/expected.txt b/crates/compact-lint/tests/fixtures/fix-rename/expected.txt
new file mode 100644
index 0000000..50c5c2a
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-rename/expected.txt
@@ -0,0 +1,2 @@
+Returns.compact:9:6: fix: renamed @return to @returns
+Returns.compact:15:6: fix: renamed @return to @returns
diff --git a/crates/compact-lint/tests/fixtures/fix-skeleton/after/Bare.compact b/crates/compact-lint/tests/fixtures/fix-skeleton/after/Bare.compact
new file mode 100644
index 0000000..77abb21
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-skeleton/after/Bare.compact
@@ -0,0 +1,59 @@
+pragma language_version >= 0.23.0;
+
+/**
+ * @module Bare
+ * @description TODO
+ */
+export module Bare {
+ import CompactStandardLibrary;
+
+ /**
+ * @description TODO
+ */
+ export ledger _count: Counter;
+
+ /**
+ * @description TODO
+ */
+ witness wit_Secret(): Bytes<32>;
+
+ /**
+ * @description TODO
+ */
+ export struct Reading {
+ value: Uint<64>
+ }
+
+ /**
+ * @description TODO
+ */
+ export enum Direction {
+ Up,
+ Down
+ }
+
+ /**
+ * TODO
+ */
+ export type Alias = Bytes<32>;
+
+ constructor() {
+ _count.resetToDefault();
+ }
+
+ /**
+ * @description TODO
+ */
+ export pure circuit double(value: Uint<8>): Uint<16> {
+ return value as Uint<16> * 2;
+ }
+
+ /**
+ * @description TODO
+ *
+ * @constraints k=?, rows=?
+ */
+ export circuit bump(): [] {
+ _count.increment(1);
+ }
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-skeleton/before/Bare.compact b/crates/compact-lint/tests/fixtures/fix-skeleton/before/Bare.compact
new file mode 100644
index 0000000..9a88c83
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-skeleton/before/Bare.compact
@@ -0,0 +1,32 @@
+pragma language_version >= 0.23.0;
+
+export module Bare {
+ import CompactStandardLibrary;
+
+ export ledger _count: Counter;
+
+ witness wit_Secret(): Bytes<32>;
+
+ export struct Reading {
+ value: Uint<64>
+ }
+
+ export enum Direction {
+ Up,
+ Down
+ }
+
+ export type Alias = Bytes<32>;
+
+ constructor() {
+ _count.resetToDefault();
+ }
+
+ export pure circuit double(value: Uint<8>): Uint<16> {
+ return value as Uint<16> * 2;
+ }
+
+ export circuit bump(): [] {
+ _count.increment(1);
+ }
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-skeleton/compact-lint.toml b/crates/compact-lint/tests/fixtures/fix-skeleton/compact-lint.toml
new file mode 100644
index 0000000..5fd3408
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-skeleton/compact-lint.toml
@@ -0,0 +1,31 @@
+version = 1
+include = ["**/*.compact"]
+
+[fix]
+placeholder = "TODO"
+
+[tags]
+forbid = ["@return"]
+rename = { "@return" = "@returns" }
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.circuit]
+tags = ["@description"]
+
+[kinds.ledger]
+tags = ["@description"]
+
+[kinds.witness]
+docs = "all"
+tags = ["@description"]
+
+[kinds.struct]
+tags = ["@description"]
+
+[kinds.enum]
+tags = ["@description"]
+
+[kinds.constructor]
+docs = "none"
diff --git a/crates/compact-lint/tests/fixtures/fix-skeleton/expected.txt b/crates/compact-lint/tests/fixtures/fix-skeleton/expected.txt
new file mode 100644
index 0000000..8dadb68
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-skeleton/expected.txt
@@ -0,0 +1,8 @@
+Bare.compact:3:1: fix: inserted doc skeleton for module `Bare`
+Bare.compact:6:3: fix: inserted doc skeleton for ledger `_count`
+Bare.compact:8:3: fix: inserted doc skeleton for witness `wit_Secret`
+Bare.compact:10:3: fix: inserted doc skeleton for struct `Reading`
+Bare.compact:14:3: fix: inserted doc skeleton for enum `Direction`
+Bare.compact:19:3: fix: inserted doc skeleton for type `Alias`
+Bare.compact:25:3: fix: inserted doc skeleton for circuit `double`
+Bare.compact:29:3: fix: inserted doc skeleton for circuit `bump`
diff --git a/crates/compact-lint/tests/fixtures/fix-unfixable/after/Malformed.compact b/crates/compact-lint/tests/fixtures/fix-unfixable/after/Malformed.compact
new file mode 100644
index 0000000..d7039c8
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-unfixable/after/Malformed.compact
@@ -0,0 +1,12 @@
+/**
+ * @module Malformed
+ * @description A module whose annotations a rewrite cannot repair.
+ */
+export module Malformed {
+ /**
+ * @description The comma is missing, and nothing can guess the measurement.
+ * @constraints k=10 rows=626
+ * @throws {Error} The forbidden tag has no replacement.
+ */
+ export circuit first(): [] { }
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-unfixable/before/Malformed.compact b/crates/compact-lint/tests/fixtures/fix-unfixable/before/Malformed.compact
new file mode 100644
index 0000000..d7039c8
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-unfixable/before/Malformed.compact
@@ -0,0 +1,12 @@
+/**
+ * @module Malformed
+ * @description A module whose annotations a rewrite cannot repair.
+ */
+export module Malformed {
+ /**
+ * @description The comma is missing, and nothing can guess the measurement.
+ * @constraints k=10 rows=626
+ * @throws {Error} The forbidden tag has no replacement.
+ */
+ export circuit first(): [] { }
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-unfixable/compact-lint.toml b/crates/compact-lint/tests/fixtures/fix-unfixable/compact-lint.toml
new file mode 100644
index 0000000..9bb3ab8
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-unfixable/compact-lint.toml
@@ -0,0 +1,15 @@
+version = 1
+include = ["**/*.compact"]
+
+# `@throws` is forbidden but has no replacement, so `fix` leaves it where it is.
+[tags]
+forbid = ["@throws"]
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.circuit]
+tags = ["@description"]
+
+[kinds.constructor]
+docs = "none"
diff --git a/crates/compact-lint/tests/fixtures/fix-unfixable/expected.txt b/crates/compact-lint/tests/fixtures/fix-unfixable/expected.txt
new file mode 100644
index 0000000..e69de29
From 46ef29a12ffb52bdbee32ffd7eb60270a790898a Mon Sep 17 00:00:00 2001
From: 0xisk <0xisk@proton.me>
Date: Wed, 16 Sep 2026 06:36:45 +0200
Subject: [PATCH 05/14] ci(rust): stop persisting checkout credentials
The default checkout leaves the job's GITHUB_TOKEN in
.git/config, so any later step or build script in the job can
read and reuse it. Nothing in this workflow talks to the remote
after checkout, so opt out.
---
.github/workflows/rust.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index a3f227a..4775a02 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -23,6 +23,8 @@ jobs:
- name: Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ persist-credentials: false
- name: Install the pinned toolchain
run: |
From f06882b5a19ac08c8aa7d0921be47a4ab7279da3 Mon Sep 17 00:00:00 2001
From: 0xisk <0xisk@proton.me>
Date: Wed, 16 Sep 2026 06:37:25 +0200
Subject: [PATCH 06/14] docs(compact-tree-sitter): correct the name field claim
Both the grammar header and the README said every declaration
node carries a `name` field. Four do not, and a consumer that
reads `name` unconditionally gets nothing back for them.
---
crates/compact-tree-sitter/README.md | 15 +++++++++------
crates/compact-tree-sitter/grammar.js | 7 +++++--
2 files changed, 14 insertions(+), 8 deletions(-)
diff --git a/crates/compact-tree-sitter/README.md b/crates/compact-tree-sitter/README.md
index 50f0d70..f07c988 100644
--- a/crates/compact-tree-sitter/README.md
+++ b/crates/compact-tree-sitter/README.md
@@ -8,12 +8,15 @@ leaves everything else opaque.
## What it covers
-- Every top-level and module-level declaration is a named node with a `name`
- field: `pragma_declaration`, `include_declaration`, `import_declaration`,
- `export_declaration`, `module_declaration`, `struct_declaration`,
- `enum_declaration`, `contract_declaration`, `implements_declaration`,
- `type_declaration`, `ledger_declaration`, `witness_declaration`,
- `constructor_declaration`, `circuit_declaration`.
+- Every top-level and module-level declaration is a named node, but the fields
+ differ per declaration. These carry a `name` field: `pragma_declaration`,
+ `import_declaration`, `module_declaration`, `struct_declaration`,
+ `enum_declaration`, `contract_declaration`, `type_declaration`,
+ `ledger_declaration`, `witness_declaration`, `circuit_declaration`.
+- The exceptions: `include_declaration` carries `path`,
+ `implements_declaration` carries `type`, `constructor_declaration` has no
+ identifier, and `export_declaration` holds its exported names as bare
+ `identifier` children with no field.
- `module_declaration` recurses: a `module_body` holds the same declarations.
- Comments are named nodes in `extras`: `line_comment`, `block_comment`,
`doc_comment`.
diff --git a/crates/compact-tree-sitter/grammar.js b/crates/compact-tree-sitter/grammar.js
index 55c2d25..2ffb901 100644
--- a/crates/compact-tree-sitter/grammar.js
+++ b/crates/compact-tree-sitter/grammar.js
@@ -2,8 +2,11 @@
// @ts-check
// Declaration-level grammar for Compact (Midnight), language version 0.26.
-// Every top-level and module-level declaration is a named node carrying a `name`
-// field; bodies, parameter lists and statements stay opaque brace-balanced runs.
+// Every top-level and module-level declaration is a named node, but the fields
+// vary: most carry `name`, while `include_declaration` carries `path`,
+// `implements_declaration` carries `type`, `constructor_declaration` has no
+// identifier at all, and `export_declaration` holds bare `identifier` children.
+// Bodies, parameter lists and statements stay opaque brace-balanced runs.
/**
* Comma-separated list with an optional trailing comma.
From e2b329d69f8e31dac0772edc871d91b67f5e37bb Mon Sep 17 00:00:00 2001
From: 0xisk <0xisk@proton.me>
Date: Wed, 16 Sep 2026 06:38:16 +0200
Subject: [PATCH 07/14] test(compact-tree-sitter): skip symlinks in the corpus
walk
A symlinked directory inside the corpus makes the recursion
loop, and a symlinked file is walked twice. Skip both.
---
crates/compact-tree-sitter/tests/corpus.rs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/crates/compact-tree-sitter/tests/corpus.rs b/crates/compact-tree-sitter/tests/corpus.rs
index 7ff16ed..f2ab924 100644
--- a/crates/compact-tree-sitter/tests/corpus.rs
+++ b/crates/compact-tree-sitter/tests/corpus.rs
@@ -41,6 +41,11 @@ fn collect_compact_files(dir: &Path, out: &mut Vec) {
let path = entry.path();
let name = entry.file_name();
+ // A symlinked directory can point back up the tree and loop forever.
+ if path.is_symlink() {
+ continue;
+ }
+
if path.is_dir() {
if !SKIPPED_DIRS.iter().any(|skipped| name == *skipped) {
collect_compact_files(&path, out);
From 334b4a98a8440e290f2dad8ee1b60efe5722939a Mon Sep 17 00:00:00 2001
From: 0xisk <0xisk@proton.me>
Date: Wed, 16 Sep 2026 06:40:49 +0200
Subject: [PATCH 08/14] fix(compact-lint): resolve explicit paths against cwd
Discovery matches exclude globs against paths relative to the
config's directory, but an explicit CLI path stayed relative to
the process cwd. Running `check archive` from a subdirectory
produced `archive/X.compact`, which never matched an exclude
written as `contracts/archive/**`, so excluded files were
silently linted. Anchor relative CLI paths at cwd first; the
display step still strips cwd, so the report is unchanged.
---
crates/compact-lint/src/check.rs | 38 ++++++++++++++++++-
crates/compact-lint/tests/check.rs | 15 ++++++++
.../exclude-explicit-dir/compact-lint.toml | 9 +++++
.../contracts/archive/Old.compact | 7 ++++
.../contracts/live/Live.compact | 7 ++++
.../exclude-explicit-dir/expected.txt | 1 +
6 files changed, 76 insertions(+), 1 deletion(-)
create mode 100644 crates/compact-lint/tests/fixtures/exclude-explicit-dir/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/exclude-explicit-dir/contracts/archive/Old.compact
create mode 100644 crates/compact-lint/tests/fixtures/exclude-explicit-dir/contracts/live/Live.compact
create mode 100644 crates/compact-lint/tests/fixtures/exclude-explicit-dir/expected.txt
diff --git a/crates/compact-lint/src/check.rs b/crates/compact-lint/src/check.rs
index 2f2d2a8..76ee393 100644
--- a/crates/compact-lint/src/check.rs
+++ b/crates/compact-lint/src/check.rs
@@ -67,7 +67,8 @@ pub fn run(options: &Options, cwd: &Path) -> Result {
let include = config.include_set(source)?;
discover::from_globs(&base, &include, &exclude)?
} else {
- discover::from_paths(&options.paths, &base, &exclude)?
+ let paths = absolute_paths(&options.paths, cwd);
+ discover::from_paths(&paths, &base, &exclude)?
};
let files: Vec = files.iter().map(|path| display_path(path, cwd)).collect();
@@ -111,7 +112,42 @@ fn resolve_config(
}
}
+/// Discovery matches the exclude globs against paths relative to the config's
+/// directory, so a CLI path has to be anchored at `cwd` first: a bare `archive`
+/// given from a subdirectory would otherwise never match `contracts/archive/**`.
+fn absolute_paths(paths: &[PathBuf], cwd: &Path) -> Vec {
+ paths
+ .iter()
+ .map(|path| {
+ if path.is_absolute() {
+ path.clone()
+ } else {
+ cwd.join(path)
+ }
+ })
+ .collect()
+}
+
/// Paths under the working directory print relative to it; anything else prints as is.
fn display_path(path: &Path, cwd: &Path) -> PathBuf {
path.strip_prefix(cwd).unwrap_or(path).to_owned()
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn a_relative_path_is_anchored_at_the_working_directory() {
+ let resolved = absolute_paths(&[PathBuf::from("archive")], Path::new("/repo/contracts"));
+
+ assert_eq!(resolved, vec![PathBuf::from("/repo/contracts/archive")]);
+ }
+
+ #[test]
+ fn an_absolute_path_is_left_alone() {
+ let resolved = absolute_paths(&[PathBuf::from("/elsewhere/X.compact")], Path::new("/repo"));
+
+ assert_eq!(resolved, vec![PathBuf::from("/elsewhere/X.compact")]);
+ }
+}
diff --git a/crates/compact-lint/tests/check.rs b/crates/compact-lint/tests/check.rs
index 452c09d..c4fc99f 100644
--- a/crates/compact-lint/tests/check.rs
+++ b/crates/compact-lint/tests/check.rs
@@ -115,6 +115,21 @@ fn an_explicit_path_replaces_the_include_globs() {
);
}
+#[test]
+fn an_exclude_glob_applies_to_an_explicit_path_given_from_a_subdirectory() {
+ let output = binary()
+ .current_dir(case("exclude-explicit-dir").join("contracts"))
+ .args(["check", "--no-format", "live", "archive"])
+ .output()
+ .expect("the binary ran");
+
+ assert_eq!(
+ String::from_utf8(output.stdout).expect("the report is UTF-8"),
+ expected("exclude-explicit-dir")
+ );
+ assert_eq!(output.status.code(), Some(EXIT_FINDINGS));
+}
+
#[test]
fn an_explicit_config_anchors_discovery_at_the_working_directory() {
let config = case("clean").join("compact-lint.toml");
diff --git a/crates/compact-lint/tests/fixtures/exclude-explicit-dir/compact-lint.toml b/crates/compact-lint/tests/fixtures/exclude-explicit-dir/compact-lint.toml
new file mode 100644
index 0000000..02f0d72
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/exclude-explicit-dir/compact-lint.toml
@@ -0,0 +1,9 @@
+version = 1
+include = ["contracts/**/*.compact"]
+exclude = ["contracts/archive/**"]
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.ledger]
+tags = ["@description"]
diff --git a/crates/compact-lint/tests/fixtures/exclude-explicit-dir/contracts/archive/Old.compact b/crates/compact-lint/tests/fixtures/exclude-explicit-dir/contracts/archive/Old.compact
new file mode 100644
index 0000000..57a9386
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/exclude-explicit-dir/contracts/archive/Old.compact
@@ -0,0 +1,7 @@
+/**
+ * @module Old
+ * @description A retired module the config excludes.
+ */
+export module Old {
+ export ledger _owner: Bytes<32>;
+}
diff --git a/crates/compact-lint/tests/fixtures/exclude-explicit-dir/contracts/live/Live.compact b/crates/compact-lint/tests/fixtures/exclude-explicit-dir/contracts/live/Live.compact
new file mode 100644
index 0000000..10781e5
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/exclude-explicit-dir/contracts/live/Live.compact
@@ -0,0 +1,7 @@
+/**
+ * @module Live
+ * @description A maintained module.
+ */
+export module Live {
+ export ledger _owner: Bytes<32>;
+}
diff --git a/crates/compact-lint/tests/fixtures/exclude-explicit-dir/expected.txt b/crates/compact-lint/tests/fixtures/exclude-explicit-dir/expected.txt
new file mode 100644
index 0000000..31799ef
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/exclude-explicit-dir/expected.txt
@@ -0,0 +1 @@
+live/Live.compact:6:3: missing-doc: ledger `_owner` has no doc comment
From 93448137ec298676f483cf6fd806dede58b0e5ea Mon Sep 17 00:00:00 2001
From: 0xisk <0xisk@proton.me>
Date: Wed, 16 Sep 2026 06:41:17 +0200
Subject: [PATCH 09/14] refactor(compact-lint): mark Tag::new must_use
A constructor with no side effects; matches the rest of Tag.
---
crates/compact-lint/src/doc.rs | 1 +
1 file changed, 1 insertion(+)
diff --git a/crates/compact-lint/src/doc.rs b/crates/compact-lint/src/doc.rs
index 685007f..4da3200 100644
--- a/crates/compact-lint/src/doc.rs
+++ b/crates/compact-lint/src/doc.rs
@@ -10,6 +10,7 @@ use serde::Deserialize;
pub struct Tag(String);
impl Tag {
+ #[must_use]
pub fn new(name: impl Into) -> Self {
Self(name.into())
}
From 3ade154d446b26eaf9d9fb40e832404ef881bce7 Mon Sep 17 00:00:00 2001
From: 0xisk <0xisk@proton.me>
Date: Wed, 16 Sep 2026 06:42:15 +0200
Subject: [PATCH 10/14] fix(compact-lint): check constraints under docs = none
The constraints rule ran after the per-kind docs gate, so
kinds.circuit.docs = "none" silently disabled it. The rule is
fixed rather than configurable, so it now runs before the gate;
only the required-tag loop stays behind it.
---
crates/compact-lint/src/rules.rs | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/crates/compact-lint/src/rules.rs b/crates/compact-lint/src/rules.rs
index 9bc0bf7..4e8cefc 100644
--- a/crates/compact-lint/src/rules.rs
+++ b/crates/compact-lint/src/rules.rs
@@ -146,6 +146,9 @@ fn check_declaration(
check_module_name(declaration, display, findings);
}
+ // The constraints rule is fixed, so it stays outside the per-kind docs policy.
+ check_constraints(declaration, display, config, strict, findings);
+
if !requires_docs {
return;
}
@@ -160,8 +163,6 @@ fn check_declaration(
));
}
}
-
- check_constraints(declaration, display, config, strict, findings);
}
/// `@module ` must name the module it documents.
@@ -341,6 +342,19 @@ mod tests {
);
}
+ #[test]
+ fn the_constraints_rule_survives_docs_none() {
+ let source =
+ "/** @description Bumps. */\nexport circuit bump(): [] { count.increment(1); }\n";
+ let mut config = circuit_config();
+ config.kinds.circuit.docs = DocsPolicy::None;
+
+ assert_eq!(
+ findings(source, &config, false),
+ ["a.compact:2:1: missing-constraints: circuit `bump` doc comment has no @constraints"]
+ );
+ }
+
#[test]
fn a_placeholder_is_a_finding_only_under_strict() {
let source = "/**\n * @description Bumps.\n * @constraints k=?, rows=?\n */\nexport circuit bump(): [] { }\n";
From 0acfa84bb211bece6ff194329ba1569811aeae57 Mon Sep 17 00:00:00 2001
From: 0xisk <0xisk@proton.me>
Date: Wed, 16 Sep 2026 06:45:39 +0200
Subject: [PATCH 11/14] fix(compact-lint): skip inserting a tag a rename
supplies
A config that forbids @return, renames it to @returns and
requires @returns produced both a rename edit and an insert
edit on the same doc comment, so fix left the comment with
@returns twice. Drop the insert when a rename in that comment
already yields the missing tag.
---
crates/compact-lint/src/fix.rs | 26 +++++++++++++++++--
crates/compact-lint/tests/fix.rs | 12 +++++++++
.../after/Supplied.compact | 12 +++++++++
.../before/Supplied.compact | 12 +++++++++
.../fix-rename-supplies-tag/compact-lint.toml | 12 +++++++++
.../fix-rename-supplies-tag/expected.txt | 1 +
6 files changed, 73 insertions(+), 2 deletions(-)
create mode 100644 crates/compact-lint/tests/fixtures/fix-rename-supplies-tag/after/Supplied.compact
create mode 100644 crates/compact-lint/tests/fixtures/fix-rename-supplies-tag/before/Supplied.compact
create mode 100644 crates/compact-lint/tests/fixtures/fix-rename-supplies-tag/compact-lint.toml
create mode 100644 crates/compact-lint/tests/fixtures/fix-rename-supplies-tag/expected.txt
diff --git a/crates/compact-lint/src/fix.rs b/crates/compact-lint/src/fix.rs
index 88e42bd..a2d21c1 100644
--- a/crates/compact-lint/src/fix.rs
+++ b/crates/compact-lint/src/fix.rs
@@ -87,9 +87,10 @@ pub fn run(options: &Options, cwd: &Path) -> Result {
})?;
let newline = newline_of(&source);
- let edits: Vec = linter
- .issues(path, &source, &target.config, false)?
+ let issues = linter.issues(path, &source, &target.config, false)?;
+ let edits: Vec = issues
.iter()
+ .filter(|issue| !supplied_by_a_rename(issue, &issues, &target.config))
.filter_map(|issue| edit_for(issue, &target.config, newline))
.collect();
@@ -109,6 +110,27 @@ pub fn run(options: &Options, cwd: &Path) -> Result {
Ok(Outcome { files })
}
+/// Whether a forbidden tag in the same doc comment is renamed to the tag this
+/// `missing-tag` issue reports.
+///
+/// Inserting it as well would leave the comment carrying the tag twice.
+fn supplied_by_a_rename(issue: &Issue, issues: &[Issue], config: &Config) -> bool {
+ let Issue::MissingTag { tag, doc, .. } = issue else {
+ return false;
+ };
+
+ issues.iter().any(|other| {
+ matches!(
+ other,
+ Issue::ForbiddenTag {
+ tag: forbidden,
+ doc: other_doc,
+ ..
+ } if other_doc == doc && config.rename_of(forbidden) == Some(tag)
+ )
+ })
+}
+
/// The repair for one issue, or `None` where the rule has no safe fix.
///
/// `constraints-format` and `constraints-placeholder` need a measurement a rewrite
diff --git a/crates/compact-lint/tests/fix.rs b/crates/compact-lint/tests/fix.rs
index fae2b52..c8b5d9e 100644
--- a/crates/compact-lint/tests/fix.rs
+++ b/crates/compact-lint/tests/fix.rs
@@ -151,6 +151,18 @@ fn a_forbidden_tag_with_a_replacement_is_renamed_in_place() {
assert_case("rename");
}
+#[test]
+fn a_rename_that_supplies_a_required_tag_suppresses_the_insert() {
+ assert_case("rename-supplies-tag");
+
+ let directory = work("rename-supplies-tag");
+ run(directory.path(), &["fix"]);
+ let fixed = std::fs::read_to_string(directory.path().join("Supplied.compact"))
+ .expect("the fixed source is readable");
+
+ assert_eq!(fixed.matches("@returns").count(), 1, "{fixed}");
+}
+
#[test]
fn a_stale_module_tag_takes_the_module_name() {
assert_case("module-name");
diff --git a/crates/compact-lint/tests/fixtures/fix-rename-supplies-tag/after/Supplied.compact b/crates/compact-lint/tests/fixtures/fix-rename-supplies-tag/after/Supplied.compact
new file mode 100644
index 0000000..a405a3b
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-rename-supplies-tag/after/Supplied.compact
@@ -0,0 +1,12 @@
+/**
+ * @module Supplied
+ * @description A module whose circuit still uses the old return tag.
+ */
+export module Supplied {
+ /**
+ * @description Does nothing.
+ * @constraints k=6, rows=28
+ * @returns {[]} - Empty tuple.
+ */
+ export circuit run(): [] { }
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-rename-supplies-tag/before/Supplied.compact b/crates/compact-lint/tests/fixtures/fix-rename-supplies-tag/before/Supplied.compact
new file mode 100644
index 0000000..fa377d5
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-rename-supplies-tag/before/Supplied.compact
@@ -0,0 +1,12 @@
+/**
+ * @module Supplied
+ * @description A module whose circuit still uses the old return tag.
+ */
+export module Supplied {
+ /**
+ * @description Does nothing.
+ * @constraints k=6, rows=28
+ * @return {[]} - Empty tuple.
+ */
+ export circuit run(): [] { }
+}
diff --git a/crates/compact-lint/tests/fixtures/fix-rename-supplies-tag/compact-lint.toml b/crates/compact-lint/tests/fixtures/fix-rename-supplies-tag/compact-lint.toml
new file mode 100644
index 0000000..815000c
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-rename-supplies-tag/compact-lint.toml
@@ -0,0 +1,12 @@
+version = 1
+include = ["**/*.compact"]
+
+[tags]
+forbid = ["@return"]
+rename = { "@return" = "@returns" }
+
+[kinds.module]
+tags = ["@module", "@description"]
+
+[kinds.circuit]
+tags = ["@description", "@returns"]
diff --git a/crates/compact-lint/tests/fixtures/fix-rename-supplies-tag/expected.txt b/crates/compact-lint/tests/fixtures/fix-rename-supplies-tag/expected.txt
new file mode 100644
index 0000000..9a3fad0
--- /dev/null
+++ b/crates/compact-lint/tests/fixtures/fix-rename-supplies-tag/expected.txt
@@ -0,0 +1 @@
+Supplied.compact:9:6: fix: renamed @return to @returns
From e64fef967070e64e874321ccce9473cdebd0dbff Mon Sep 17 00:00:00 2001
From: 0xisk <0xisk@proton.me>
Date: Wed, 16 Sep 2026 06:46:59 +0200
Subject: [PATCH 12/14] fix(compact-lint): validate the fix placeholder
The placeholder is written onto a `*` line of a generated doc
comment. An empty one produced a blank tag line, a multi-line
one broke the comment layout, and one containing `*/` closed
the comment early and corrupted the source. Reject all three at
load time instead.
---
crates/compact-lint/src/config.rs | 63 ++++++++++++++++++++++++++++++-
1 file changed, 62 insertions(+), 1 deletion(-)
diff --git a/crates/compact-lint/src/config.rs b/crates/compact-lint/src/config.rs
index 002c6d1..6b68d94 100644
--- a/crates/compact-lint/src/config.rs
+++ b/crates/compact-lint/src/config.rs
@@ -38,6 +38,12 @@ pub enum ConfigError {
"config {path} lists tag {tag:?}; tags are spelled with their `@`, like `@description`"
)]
Tag { path: PathBuf, tag: String },
+ #[error("config {path} has an invalid fix.placeholder {placeholder:?}: {reason}")]
+ Placeholder {
+ path: PathBuf,
+ placeholder: String,
+ reason: &'static str,
+ },
#[error("config {path} has an invalid glob {pattern:?}")]
Glob {
path: PathBuf,
@@ -198,6 +204,14 @@ impl Config {
});
}
+ if let Some(reason) = placeholder_defect(&config.fix.placeholder) {
+ return Err(ConfigError::Placeholder {
+ path: path.to_owned(),
+ placeholder: config.fix.placeholder.clone(),
+ reason,
+ });
+ }
+
Ok(config)
}
@@ -245,6 +259,20 @@ impl Config {
}
}
+/// Why a placeholder cannot go on a `*` line of a doc comment, or `None` when it can.
+fn placeholder_defect(placeholder: &str) -> Option<&'static str> {
+ if placeholder.trim().is_empty() {
+ return Some("it is empty");
+ }
+ if placeholder.contains(['\n', '\r']) {
+ return Some("it spans more than one line");
+ }
+ if placeholder.contains("*/") {
+ return Some("`*/` would close the comment early");
+ }
+ None
+}
+
fn glob_set(patterns: &[String], path: &Path) -> Result {
let mut builder = GlobSetBuilder::new();
for pattern in patterns {
@@ -276,9 +304,21 @@ pub fn discover(start: &Path) -> Option {
#[cfg(test)]
mod tests {
- use super::{Config, DocsPolicy, SUPPORTED_VERSION};
+ use super::{CONFIG_FILE_NAME, Config, DocsPolicy, SUPPORTED_VERSION};
use crate::doc::Tag;
use crate::model::DeclKind;
+ use tempfile::TempDir;
+
+ /// Loads `snippet` as a config file and returns the rejection it produced.
+ fn rejection(snippet: &str) -> String {
+ let directory = TempDir::new().expect("a temporary directory is available");
+ let path = directory.path().join(CONFIG_FILE_NAME);
+ std::fs::write(&path, snippet).expect("the config is writable");
+
+ Config::load(&path)
+ .expect_err("the config is invalid")
+ .to_string()
+ }
#[test]
fn defaults_require_docs_on_exported_declarations_only() {
@@ -360,6 +400,27 @@ mod tests {
assert_eq!(config.fix.placeholder, "FIXME");
}
+ #[test]
+ fn an_empty_fix_placeholder_is_rejected() {
+ let error = rejection("[fix]\nplaceholder = \" \"\n");
+
+ assert!(error.contains("is empty"), "{error}");
+ }
+
+ #[test]
+ fn a_multi_line_fix_placeholder_is_rejected() {
+ let error = rejection("[fix]\nplaceholder = \"TO\\nDO\"\n");
+
+ assert!(error.contains("more than one line"), "{error}");
+ }
+
+ #[test]
+ fn a_fix_placeholder_closing_the_comment_is_rejected() {
+ let error = rejection("[fix]\nplaceholder = \"TODO */\"\n");
+
+ assert!(error.contains("close the comment"), "{error}");
+ }
+
#[test]
fn an_unknown_kind_is_rejected() {
let error =
From c4737e6b95bbd5ccc9e4b6dfd1d3f09a2d011f77 Mon Sep 17 00:00:00 2001
From: 0xisk <0xisk@proton.me>
Date: Wed, 16 Sep 2026 06:48:02 +0200
Subject: [PATCH 13/14] fix(compact-lint): validate rename against forbid
A rename only fires from a forbidden-tag finding, so a key that
tags.forbid does not list is dead config the author expects to
work. A value that tags.forbid does list makes fix rewrite one
finding into another, so a second run reports the replacement.
Reject both at load time.
---
crates/compact-lint/README.md | 7 +++---
crates/compact-lint/src/config.rs | 41 +++++++++++++++++++++++++++++++
2 files changed, 45 insertions(+), 3 deletions(-)
diff --git a/crates/compact-lint/README.md b/crates/compact-lint/README.md
index 0c4f3b2..5f2cfd6 100644
--- a/crates/compact-lint/README.md
+++ b/crates/compact-lint/README.md
@@ -159,9 +159,10 @@ Defaults when no config file is found:
`"all"` or `"none"` there.
- `exclude` filters files found by walking a directory. A file named on the command
line is always checked.
-- `tags.rename` maps a forbidden tag to the tag `fix` writes in its place. Both spellings
- are validated like every other tag. An unmapped forbidden tag is reported, never
- rewritten.
+- `tags.rename` maps a forbidden tag to the tag `fix` writes in its place. Every key
+ must appear in `tags.forbid` and no value may, or the config is rejected. Both
+ spellings are validated like every other tag. An unmapped forbidden tag is reported,
+ never rewritten.
- `fix.placeholder` is the text `fix` writes where it has no value of its own.
`examples/compact-contracts.toml` is the config for OpenZeppelin/compact-contracts.
diff --git a/crates/compact-lint/src/config.rs b/crates/compact-lint/src/config.rs
index 6b68d94..3bddff2 100644
--- a/crates/compact-lint/src/config.rs
+++ b/crates/compact-lint/src/config.rs
@@ -38,6 +38,14 @@ pub enum ConfigError {
"config {path} lists tag {tag:?}; tags are spelled with their `@`, like `@description`"
)]
Tag { path: PathBuf, tag: String },
+ #[error("config {path} renames {tag}, which tags.forbid does not list")]
+ RenameUnforbidden { path: PathBuf, tag: String },
+ #[error("config {path} renames {from} to {to}, which tags.forbid also lists")]
+ RenameToForbidden {
+ path: PathBuf,
+ from: String,
+ to: String,
+ },
#[error("config {path} has an invalid fix.placeholder {placeholder:?}: {reason}")]
Placeholder {
path: PathBuf,
@@ -204,6 +212,22 @@ impl Config {
});
}
+ for (from, to) in &config.tags.rename {
+ if !config.tags.forbid.contains(from) {
+ return Err(ConfigError::RenameUnforbidden {
+ path: path.to_owned(),
+ tag: from.to_string(),
+ });
+ }
+ if config.tags.forbid.contains(to) {
+ return Err(ConfigError::RenameToForbidden {
+ path: path.to_owned(),
+ from: from.to_string(),
+ to: to.to_string(),
+ });
+ }
+ }
+
if let Some(reason) = placeholder_defect(&config.fix.placeholder) {
return Err(ConfigError::Placeholder {
path: path.to_owned(),
@@ -400,6 +424,23 @@ mod tests {
assert_eq!(config.fix.placeholder, "FIXME");
}
+ #[test]
+ fn a_rename_of_a_tag_that_is_not_forbidden_is_rejected() {
+ let error = rejection("[tags]\nrename = { \"@return\" = \"@returns\" }\n");
+
+ assert!(error.contains("@return"), "{error}");
+ assert!(error.contains("tags.forbid does not list"), "{error}");
+ }
+
+ #[test]
+ fn a_rename_onto_a_forbidden_tag_is_rejected() {
+ let error = rejection(
+ "[tags]\nforbid = [\"@return\", \"@returns\"]\nrename = { \"@return\" = \"@returns\" }\n",
+ );
+
+ assert!(error.contains("tags.forbid also lists"), "{error}");
+ }
+
#[test]
fn an_empty_fix_placeholder_is_rejected() {
let error = rejection("[fix]\nplaceholder = \" \"\n");
From aa06bbdcab7d57964f2fb70608351f86ca01cbc4 Mon Sep 17 00:00:00 2001
From: 0xisk <0xisk@proton.me>
Date: Wed, 16 Sep 2026 06:49:23 +0200
Subject: [PATCH 14/14] fix(compact-lint): write through a unique temp file
The staging file was `.compact.tmp` beside the source and
written with fs::write, so a pre-existing file of that name was
truncated, and the predictable name let anything else with write
access to the directory plant one. Create a sibling
`....tmp` exclusively instead, write through
the handle, and remove it when a later step fails.
---
crates/compact-lint/src/fix.rs | 77 +++++++++++++++++++++++++-------
crates/compact-lint/tests/fix.rs | 15 +++++++
2 files changed, 76 insertions(+), 16 deletions(-)
diff --git a/crates/compact-lint/src/fix.rs b/crates/compact-lint/src/fix.rs
index a2d21c1..4e44f5f 100644
--- a/crates/compact-lint/src/fix.rs
+++ b/crates/compact-lint/src/fix.rs
@@ -1,6 +1,10 @@
//! The `fix` subcommand: turn the fixable issues into edits and rewrite the files.
+use std::ffi::{OsStr, OsString};
+use std::fs::{File, OpenOptions};
+use std::io::{ErrorKind, Write};
use std::path::{Path, PathBuf};
+use std::time::{SystemTime, UNIX_EPOCH};
use thiserror::Error;
@@ -308,27 +312,68 @@ fn skeleton(
format!("{}{newline}{margin}", lines.join(newline))
}
+/// How many unique names to try before giving up on the temporary file.
+const TEMPORARY_ATTEMPTS: u32 = 8;
+
/// Writes through a sibling temporary file, so a failed write never truncates the source.
fn write_atomically(path: &Path, text: &str) -> Result<(), FixError> {
- let temporary = path.with_extension("compact.tmp");
-
- std::fs::write(&temporary, text).map_err(|source| FixError::Write {
- path: temporary.clone(),
- source,
- })?;
- let permissions = std::fs::metadata(path)
- .map_err(|source| FixError::Read {
+ let (temporary, mut handle) = create_temporary(path)?;
+
+ let write = handle
+ .write_all(text.as_bytes())
+ .and_then(|()| handle.sync_all())
+ .and_then(|()| std::fs::metadata(path))
+ .and_then(|metadata| std::fs::set_permissions(&temporary, metadata.permissions()))
+ .and_then(|()| std::fs::rename(&temporary, path));
+
+ write.map_err(|source| {
+ // The rename never happened, so the temporary file is ours to clean up.
+ let _ = std::fs::remove_file(&temporary);
+ FixError::Write {
path: path.to_owned(),
source,
- })?
- .permissions();
- std::fs::set_permissions(&temporary, permissions).map_err(|source| FixError::Write {
- path: temporary.clone(),
- source,
- })?;
- std::fs::rename(&temporary, path).map_err(|source| FixError::Write {
+ }
+ })
+}
+
+/// Creates a sibling temporary file under a name nothing else holds.
+///
+/// A predictable name lets another process plant a file the write would truncate, so
+/// the name carries the pid and a timestamp and the file is created exclusively.
+fn create_temporary(path: &Path) -> Result<(PathBuf, File), FixError> {
+ let directory = path.parent().unwrap_or(Path::new("."));
+ let stem = path.file_name().unwrap_or(OsStr::new("source"));
+ let pid = std::process::id();
+
+ let mut last = None;
+ for attempt in 0..TEMPORARY_ATTEMPTS {
+ let nanos = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .map_or(0, |since| since.subsec_nanos());
+ let mut name = OsString::from(".");
+ name.push(stem);
+ name.push(format!(".{pid}.{nanos}.{attempt}.tmp"));
+ let candidate = directory.join(name);
+
+ match OpenOptions::new()
+ .write(true)
+ .create_new(true)
+ .open(&candidate)
+ {
+ Ok(handle) => return Ok((candidate, handle)),
+ Err(error) if error.kind() == ErrorKind::AlreadyExists => last = Some(error),
+ Err(source) => {
+ return Err(FixError::Write {
+ path: path.to_owned(),
+ source,
+ });
+ }
+ }
+ }
+
+ Err(FixError::Write {
path: path.to_owned(),
- source,
+ source: last.unwrap_or_else(|| ErrorKind::AlreadyExists.into()),
})
}
diff --git a/crates/compact-lint/tests/fix.rs b/crates/compact-lint/tests/fix.rs
index c8b5d9e..a92aa1f 100644
--- a/crates/compact-lint/tests/fix.rs
+++ b/crates/compact-lint/tests/fix.rs
@@ -226,6 +226,21 @@ fn an_explicit_config_replaces_the_one_beside_the_sources() {
);
}
+#[test]
+fn a_file_named_like_the_old_temporary_survives_a_fix() {
+ let directory = work("rename");
+ let planted = directory.path().join("Returns.compact.tmp");
+ std::fs::write(&planted, "not mine to truncate\n").expect("the plant is writable");
+
+ let (code, _) = run(directory.path(), &["fix"]);
+
+ assert_eq!(code, 0);
+ assert_eq!(
+ std::fs::read_to_string(&planted).expect("the plant is still there"),
+ "not mine to truncate\n"
+ );
+}
+
#[test]
fn a_missing_path_exits_two() {
let directory = work("rename");