Compare commits

...

950 commits

Author SHA1 Message Date
Christian Clason de98c6c970 build(deps): bump rquickjs to v0.13.0
Some checks failed
CI / sanitize (push) Failing after 15s
CI / build (push) Failing after 16s
CI / check-wasm-stdlib (push) Failing after 15s
CI / checks (push) Has been cancelled
2026-09-09 01:25:17 +02:00
Christian Clason 8351896bea build(deps): cargo update
Some checks failed
CI / checks (push) Waiting to run
CI / sanitize (push) Failing after 15s
CI / build (push) Failing after 16s
CI / check-wasm-stdlib (push) Failing after 15s
2026-09-08 09:46:26 +02:00
Will Lillis 072f68c829 feat(generate)!: derive PartialEq + Eq on GenerateError
Some checks failed
CI / checks (push) Waiting to run
CI / sanitize (push) Failing after 15s
CI / build (push) Failing after 16s
CI / check-wasm-stdlib (push) Failing after 15s
Also use `IoError` more consistently throughout the rest of the project
where its straightforward to do so.

BREAKING CHANGE: Changes public error types for config, generate, and
loader crates.
2026-09-07 17:53:09 -04:00
dependabot[bot] badf2882c6 build(deps): bump the npm group across 1 directory with 5 updates
Bumps the npm group with 5 updates in the /lib/binding_web directory:

| Package | From | To |
| --- | --- | --- |
| [@types/emscripten](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/emscripten) | `1.41.5` | `1.41.6` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.4.0` | `26.4.1` |
| [eslint](https://github.com/eslint/eslint) | `10.9.1` | `10.10.0` |
| [tsx](https://github.com/privatenumber/tsx) | `4.23.12` | `4.23.13` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.68.0` | `8.69.0` |



Updates `@types/emscripten` from 1.41.5 to 1.41.6
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/emscripten)

Updates `@types/node` from 26.4.0 to 26.4.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `eslint` from 10.9.1 to 10.10.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.9.1...v10.10.0)

Updates `tsx` from 4.23.12 to 4.23.13
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.23.12...v4.23.13)

Updates `typescript-eslint` from 8.68.0 to 8.69.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.69.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@types/emscripten"
  dependency-version: 1.41.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@types/node"
  dependency-version: 26.4.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: eslint
  dependency-version: 10.10.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: tsx
  dependency-version: 4.23.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: typescript-eslint
  dependency-version: 8.69.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-07 23:46:17 +02:00
dependabot[bot] 9c2c5714cd build(deps): bump cc from 1.4.4 to 1.4.5 in the cargo group
Bumps the cargo group with 1 update: [cc](https://github.com/rust-lang/cc-rs).


Updates `cc` from 1.4.4 to 1.4.5
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.4.4...cc-v1.4.5)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.4.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-07 23:45:04 +02:00
Will Lillis f430449ab9 fix(rust): address manual-readme lint
Some checks failed
CI / sanitize (push) Failing after 15s
CI / build (push) Failing after 16s
CI / check-wasm-stdlib (push) Failing after 14s
CI / checks (push) Has been cancelled
Cargo automatically resolves this, so the `Cargo.toml` entries are
redundant.
2026-09-04 14:48:46 -04:00
Will Lillis 068db86ca9 fix(rust): address new assert-is-empty nightly lint
The resulting code is arguably uglier, but there is a strong argument
for the improved message when the assert trips.
2026-09-04 14:48:46 -04:00
cui fliter ee0c20b1f5
fix(cli)!: fix query test capture range matching across lines (#5917)
Some checks failed
CI / checks (push) Waiting to run
CI / sanitize (push) Failing after 15s
CI / build (push) Failing after 16s
CI / check-wasm-stdlib (push) Failing after 15s
Signed-off-by: cuishuang <imcusg@gmail.com>
BREAKING CHANGE: changes the signature of `assert_expected_captures`, which is exposed publicly when tree-sitter-cli is consumed as a library.
2026-09-03 23:25:21 -04:00
dependabot[bot] 702ba49a6a build(deps): bump @humanfs/node in /crates/cli/eslint
Some checks failed
CI / checks (push) Waiting to run
CI / sanitize (push) Failing after 14s
CI / build (push) Failing after 16s
CI / check-wasm-stdlib (push) Failing after 15s
Bumps [@humanfs/node](https://github.com/humanwhocodes/humanfs/tree/HEAD/packages/node) from 0.16.7 to 0.16.8.
- [Release notes](https://github.com/humanwhocodes/humanfs/releases)
- [Changelog](https://github.com/humanwhocodes/humanfs/blob/main/packages/node/CHANGELOG.md)
- [Commits](https://github.com/humanwhocodes/humanfs/commits/node-v0.16.8/packages/node)

---
updated-dependencies:
- dependency-name: "@humanfs/node"
  dependency-version: 0.16.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-03 16:35:52 +02:00
dependabot[bot] b9c5eb2b73 build(deps): bump @humanfs/node in /lib/binding_web
Bumps [@humanfs/node](https://github.com/humanwhocodes/humanfs/tree/HEAD/packages/node) from 0.16.7 to 0.16.8.
- [Release notes](https://github.com/humanwhocodes/humanfs/releases)
- [Changelog](https://github.com/humanwhocodes/humanfs/blob/main/packages/node/CHANGELOG.md)
- [Commits](https://github.com/humanwhocodes/humanfs/commits/node-v0.16.8/packages/node)

---
updated-dependencies:
- dependency-name: "@humanfs/node"
  dependency-version: 0.16.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-03 16:35:42 +02:00
Yudai Takada 351bd71e52
fix(lib): decode UTF-16 surrogate pairs with input endianness (#5912)
Some checks failed
CI / checks (push) Waiting to run
CI / sanitize (push) Failing after 16s
CI / build (push) Failing after 16s
CI / check-wasm-stdlib (push) Failing after 15s
Problem: Parsing UTF-16BE source containing supplementary-plane characters, such as `let emoji = "😀"`, decoded the emoji as two isolated surrogates on little-endian hosts, causing incorrect lexer lookahead and potentially shifted token boundaries.

Soluton: Fix the trailing surrogate byte-order conversion in both UTF-16LE and UTF-16BE decoders and adds a regression test for U+1F600.
2026-09-03 08:21:08 +02:00
dependabot[bot] c206ad1e6a build(deps): bump the npm group across 1 directory with 3 updates
Some checks failed
CI / sanitize (push) Failing after 15s
CI / build (push) Failing after 15s
CI / check-wasm-stdlib (push) Failing after 15s
CI / checks (push) Has been cancelled
Bumps the npm group with 3 updates in the /lib/binding_web directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [eslint](https://github.com/eslint/eslint) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `@types/node` from 26.2.0 to 26.4.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `eslint` from 10.9.0 to 10.9.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.9.0...v10.9.1)

Updates `typescript-eslint` from 8.67.0 to 8.68.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.68.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 26.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: eslint
  dependency-version: 10.9.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: typescript-eslint
  dependency-version: 8.68.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 23:52:08 +02:00
dependabot[bot] 51da0190f5 ci: bump vmactions/omnios-vm from 1.3.4 to 1.3.6 in the actions group
Bumps the actions group with 1 update: [vmactions/omnios-vm](https://github.com/vmactions/omnios-vm).


Updates `vmactions/omnios-vm` from 1.3.4 to 1.3.6
- [Release notes](https://github.com/vmactions/omnios-vm/releases)
- [Commits](https://github.com/vmactions/omnios-vm/compare/v1.3.4...v1.3.6)

---
updated-dependencies:
- dependency-name: vmactions/omnios-vm
  dependency-version: 1.3.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 23:51:47 +02:00
Amaan Qureshi e5cbeb74eb nix: fix the flake
Some checks failed
CI / checks (push) Waiting to run
CI / sanitize (push) Failing after 15s
CI / build (push) Failing after 16s
CI / check-wasm-stdlib (push) Failing after 14s
Deploy Docs / deploy-docs (push) Has been cancelled
Check Wasm Exports / check-wasm-exports (push) Has been cancelled
2026-08-30 17:33:12 -04:00
Amaan Qureshi ea9ddbe190 docs: replace mdbook-admonish with mdBook admonitions 2026-08-30 17:33:12 -04:00
Will Lillis aa1b924997 fix(generate): defer token rewrites until lexical expansion
In a future feature, `RulePool` nodes can be shared between lexical and
syntax roots. No grammars generated by grammar.js/grammra.json are affected
by this issue, as they lead to a tree pool rather than a DAG.

With a DAG `RulePool` representation, however, rewriting tokens during
extraction can destroy a token body before lexical expansion consumes
it and can renumber shared syntax nodes more than once.

Record terminal rewrites while inspecting the original pool, expand tokens
and separators first, then commit the rewrites and renumber each reachable
syntax node once. Resolve grammar metadata against the pending rewrites
before mutating the pool.
2026-08-30 14:23:29 -04:00
Will Lillis 1b64a459ff perf(generate): flatten Choices directly into the children tail 2026-08-30 14:23:29 -04:00
Will Lillis 1e070eb28e perf(generate): build Seq and Repeat child ranges without temporaries
`try_seq` reserves a `Seq`'s child range up front and fills slots as
recursion produces each member. The `Repeat` arm builds
`Choice(repeat(x), blank)` directly, skipping the `choice` helper.

Slightly faster and less memory used.
2026-08-30 14:23:29 -04:00
Christian Clason 91e875ad7b chore: start working on v0.28 2026-08-30 19:50:28 +02:00
Arne Bertrand 6070dbfefd
fix(nix): remove Dockerfile reference from flake (#5892)
Fixup for https://github.com/tree-sitter/tree-sitter/pull/5872.
2026-08-30 16:49:12 +00:00
Christian Clason f52e78cb9f build(deps): cargo update
Some checks failed
CI / checks (push) Waiting to run
CI / sanitize (push) Failing after 15s
CI / build (push) Failing after 15s
CI / check-wasm-stdlib (push) Failing after 14s
2026-08-30 09:29:09 +02:00
Will Lillis 49dbf70ebd fix(rust): ensure C returned pointers are non-null in
`Node::grammar_name` and `Node::kind`
2026-08-30 02:58:20 -04:00
Will Lillis 9540ea3b10 fix(lib): reject an old_tree from a different language 2026-08-30 02:58:20 -04:00
Will Lillis 3e7be91c46 fix(rust): reject out-of-range node kind ids
`node_kind_is_named`, `node_kind_is_visible` and `node_kind_is_supertype`
forwarded their `u16` straight to `ts_language_symbol_type`, which indexes
`symbol_metadata` unchecked. Bound the id by `node_kind_count()`.
2026-08-30 02:58:20 -04:00
Will Lillis f47170927d fix(lib): bounds-check symbol and state ids in language lookups
`ts_language_next_state` indexed the parse table with the caller's `state`
and `symbol` without validating either, and `ts_language_subtypes` read
`symbol_metadata` before checking `supertype` was in range.
2026-08-30 02:58:20 -04:00
Will Lillis 5ddf71b9f4 fix(rust)!: expose QueryMatch::captures as a method
The field handed out a slice whose lifetime outlived the streaming-iterator
loan it came from. An accessor reborrowing through `&self` bounds it correctly.

BREAKING CHANGE: In the rust bindings, replace `m.captures` with `m.captures()`.
2026-08-30 02:58:20 -04:00
cuishuang efa0dd9a36 fix(cli): correct zero-based positions for --edits
Some checks failed
CI / checks (push) Waiting to run
CI / sanitize (push) Failing after 15s
CI / build (push) Failing after 15s
CI / check-wasm-stdlib (push) Failing after 15s
Signed-off-by: cuishuang <imcusg@gmail.com>
2026-08-29 21:06:30 -04:00
Christian Clason 16d5e9d227 ci(checks): add lint-toml step using taplo
Some checks failed
CI / checks (push) Waiting to run
CI / build (push) Failing after 15s
CI / check-wasm-stdlib (push) Failing after 14s
CI / sanitize (push) Failing after 14s
Deploy Docs / deploy-docs (push) Has been cancelled
2026-08-29 11:10:37 +02:00
Will Lillis 3ac04a64f7 docs: add taplo instructions 2026-08-29 11:10:37 +02:00
Will Lillis d9cb739430 fix(toml): drop align_entries = true from .taplo.toml
This key was being applied inconsistently depending on nesting level.
2026-08-29 11:10:37 +02:00
Will Lillis 7a2dd61a7c fix(xtask): preserve format padding when updating Cargo.toml files 2026-08-29 11:10:37 +02:00
Christian Clason a520ad0fc3 chore: remove .dockerignore
Fixup for #5872, which missed the hidden file.
2026-08-29 10:30:35 +02:00
Christian Clason da219f40f8 chore: remove Zed editor config
Problem: Editor-specific settings should not be part of the global
repository. (Only config files that control standard tooling in order to
enforce, e.g., formatting policies are appropriate to commit -- and only
if covered by CI.)

Solution: Drop the Zed config files from the repo.
2026-08-29 10:30:35 +02:00
Christian Clason 664e9a6786 build(deps)!: bump wasi-sdk to v34
Some checks failed
CI / checks (push) Waiting to run
Deploy Docs / deploy-docs (push) Waiting to run
CI / sanitize (push) Failing after 15s
CI / build (push) Failing after 16s
CI / check-wasm-stdlib (push) Failing after 15s
Check Bindgen Output / check-bindgen (push) Has been cancelled
Check Wasm Exports / check-wasm-exports (push) Has been cancelled
Breaking change: LLVM 23 replaces `wasm32-wasi` target with
`wasm32-wasip1`, requiring updating the flags used by
`tree-sitter build --wasm`.
2026-08-27 10:35:19 +02:00
Christian Clason fcef970e81 build(deps): bump wasmtime-c-api to v48.0.1 2026-08-27 09:37:57 +02:00
Kyuhwan Lee 8933cb7b40 docs(web): add WASM version compatibility section
Add documentation about version compatibility requirements between
web-tree-sitter parser ABIs for WASM file generation.

This helps users understand why Language.load() might fail when using
pre-built WASM files from third-party packages that were built with
incompatible tree-sitter-cli versions.

Co-authored-by: Will Lillis <will.lillis24@gmail.com>
2026-08-25 17:34:56 -04:00
Will Lillis 82639170a7 docs: specify Emscripten version constraints for web binding builds 2026-08-25 17:34:37 -04:00
dependabot[bot] 129d8cd141 build(deps): bump eslint
Bumps the npm group with 1 update in the /lib/binding_web directory: [eslint](https://github.com/eslint/eslint).


Updates `eslint` from 10.8.1 to 10.9.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.8.1...v10.9.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 10.9.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-25 09:59:01 +02:00
dependabot[bot] f3a9521894 build(deps): bump cc from 1.4.3 to 1.4.4 in the cargo group
Bumps the cargo group with 1 update: [cc](https://github.com/rust-lang/cc-rs).


Updates `cc` from 1.4.3 to 1.4.4
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.4.3...cc-v1.4.4)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.4.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-25 09:58:43 +02:00
Peter Stenger 0be5f898ad fix(web): map Node-only specifiers to false via browser field
Browser bundlers (esbuild, webpack, vite, rollup) statically discover the
`await import("fs/promises")` and `await import("module")` calls in the
shipped ESM bundle and try to resolve them, even though both sit in
Node-only branches that never execute in a browser.

Adding a top-level `browser` field tells bundlers to substitute empty
stubs for these specifiers when targeting the browser. The runtime gates
(`globalThis.process?.versions.node` and `ENVIRONMENT_IS_NODE`) already
prevent the stubs from ever being invoked.

Fixes #5545.
2026-08-25 00:27:50 -04:00
katrinafyi ac17c8d003 playground: set default LANGUAGE_BASE_URL to "." to fix subfolder hosting
Previously with the empty string base URL, the playground would not work when hosted at a subfolder of the domain. This is because playground.js has this code:

      const url = `${LANGUAGE_BASE_URL}/tree-sitter-${newLanguageName}.wasm`;

With an empty string, it would look for wasm at the root of the website and 404.

Github code search shows that this is an issue which lots of people have run into, and everyone has to work around it: https://github.com/search?q=%2FLANGUAGE_BASE_URL+%3D+%22%22%2F+-language%3AHTML&type=code

Related to https://www.github.com/tree-sitter/tree-sitter/issues/5230
2026-08-24 19:41:26 -04:00
Will Lillis c6c137a329 fix(generate): avoid mutating inherited reserved sets
Copy the base grammar's reserved set map before adding or replacing sets
in a derived grammar.
2026-08-23 17:08:31 -04:00
Will Lillis 1cb91662a5 fix(generate): validate symbol-only grammar fields
Require `word`, `conflicts`, `inline`, and `supertypes` callbacks
to return named grammar symbols instead of silently accepting values
that produce undefined names.

Restrict `precedences` lists to named rules and precedence names, and
preserve the existing handling of undefined and duplicate `inline`
rules.
2026-08-23 17:08:31 -04:00
Mike Arndt 7ca101cb7e fix(cli): align grammar DSL declarations with runtime
Model grammar() as returning the evaluated grammar beneath a `grammar`
property, with callbacks replaced by their normalized values and
runtime-initialized fields represented as required properties.

Correct the evaluated rule types for named precedences and regular
expression flags. Narrow symbol-only callbacks such as `conflicts`,
`inline`, `supertypes`, and `word`, and expose the inherited values
passed to `extras` and `reserved` callbacks.

Co-authored-by: Will Lillis <will.lillis24@gmail.com>
2026-08-23 17:08:31 -04:00
Will Lillis dad7d0bd88 generate: use a single buffer for the string pool's backing store
This representation slightly lowers walltime and allocations, but also
makes `StrPool` `Send`.
2026-08-23 14:15:16 -04:00
Will Lillis f9dcb005cd fix(generate)!: use node identities in node-types bookkeeping
Some node-types bookkeeping still used the type name without its named
status. This caused anonymous nodes to inherit extra metadata from named
nodes with the same text, and could create false supertype cycles when a
named supertype and anonymous alias shared a name.

Key extra metadata and supertype dependencies by the complete
(type, named) identity.

Reject named aliases that collide with a canonical supertype. Such an
identity cannot be represented as both a concrete node and an abstract
supertype in node-types.json, and previously caused generation to panic.

All output entries now come from the map keyed by (type, named), so no
two entries can reach the final sort with the same identity. Remove the
unreachable root and extra tie-breakers and the now-redundant deduplication.

BREAKING CHANGE: Alters a public error enum for the generate crate.
2026-08-23 13:40:32 -04:00
Will Lillis 78267a0a96 fix(generate): merge anonymous token aliases in node-types.json
When an anonymous token alias and an anonymous syntax-node alias have
the same name, node-types.json emits two entries with the same type and
named status. For example,

```
alias($._node, "same")
```
and

```
alias("!", "same")
```

produce separate anonymous "same" entries. Add anonymous tokens to
the node-type map instead. When a token shares an identity with a
structured node, retain its possible children and fields but mark
them as optional because the token appearance is a leaf.
2026-08-23 13:40:32 -04:00
Will Lillis 00beb27f83 fix(generate): distinguish named and anonymous node types
A node-types entry is identified by both its type name and its `named`
value. The generator only keyed accumulated entries by name, so aliases
like

    alias($._node, $.same)
    alias($._node, "same")

were incorrectly merged into one entry. Whichever alias was processed
first determined the entry's `named` value, and the structures of two
distinct node types could be combined.

Key accumulated entries by `NodeTypeRef` so named and anonymous nodes
with the same type name remain separate.
2026-08-23 13:40:32 -04:00
Will Lillis 7bd12334c4 fix(generate): include aliased supertypes in node-types.json
Aliasing a supertype makes the aliased occurrence appear as a regular
node in the syntax tree. For example,

```
alias($.expression, $.expression_target)
```

can produce:

```
(expression_target (identifier))
```

Previously, node-types generation skipped the source supertype entirely.
It could reference `expression_target` as a field or child type without
emitting a corresponding top-level entry for it.

Continue emitting the canonical `expression` entry with its `subtypes`,
but pass aliased appearances through regular node generation so their
children and fields are recorded and merged normally.
2026-08-23 13:40:32 -04:00
Will Lillis 54a46eb49b fix(rust): remove must_use attribute from Tree::changed_ranges
`ExactSizeIterator` implements `std::iter::Iterator`, which is already
`must_use`.
2026-08-23 09:45:42 +02:00
Christian Clason 2452dc7a71 chore: remove Dockerfile
Problem: The committed Dockerfile is not used by the standard tooling in
this repo and therefore bitrots freely, which increases maintenance
burden on the (non-Docker-using) maintainers.

Solution: Drop the Dockerfile from the repo.
2026-08-23 00:35:12 -04:00
Amaan Qureshi 74b7d0c951 feat!: add an eof function
This commit adds an `eof()` function for grammars, which is easier to
use than the NUL byte directly. It compiles down to a constraint that
the enclosing production can only reduce at end of input, not to a
shiftable token.

BREAKING CHANGE: Public error types for `tree-sitter-generate` were modified.

Co-authored-by: Will Lillis <will.lillis24@gmail.com>
2026-08-21 20:02:58 -04:00
Will Lillis baad4174e5 fix(generate)!: hold IoError inside GenerateError::GrammarPath 2026-08-20 22:11:42 -04:00
Will Lillis cde84508ac fix(generate)!: error when nonexistent _file_ path is passed to
`generate_parser_in_directory`.
2026-08-20 22:11:42 -04:00
Max Brunsfeld 43623ec9bf
Upgrade to latest wasmtime, improve robustness and perf of wasm-based parsing (#5847)
* build(deps): upgrade wasmtime C API to 48.0.0

Upgrade the Rust and Zig Wasmtime dependencies and enable reference values with the null GC collector.

Wasmtime 48 requires a newer Rust toolchain, whose Clippy version identifies three item helpers that can be const. Mark them const so the workspace continues to pass Clippy with warnings denied.

* feat(benchmark): support Wasm grammars

* perf(wasm): cache language function handles

* fix(wasm): improve validation and failure cleanup

Bounds-check dylink metadata parsing, require exact import and export names, and restore memory and function-table allocation offsets when language loading fails.

* fix(wasm): copy the complete supertype map
2026-08-20 21:02:11 +00:00
Will Lillis fe3fe327e2 fix(generate): correct test assertion for inlined supertype warning 2026-08-20 02:47:10 -04:00
Will Lillis 03ae6710cd fix(ci): run all workspace tests
Without `--workspace`, all generate tests were silently skipped.
2026-08-20 02:47:10 -04:00
Amaan Qureshi 067f85e5a2 generate: hold StrIds in node-types JSON output
The JSON output types now hold ids from the string pool instead of
cloning into owned `String`s, which makes `NodeTypeJSON` `Copy` and
drops most allocations when building the output.

Co-authored-by: Will Lillis <will.lillis24@gmail.com>
2026-08-19 20:30:17 -04:00
Amaan Qureshi 5800563fe7 generate: refactor get_variable_info and generate_node_types_json
This splits both functions into small, focused helpers with no
behavioral change.
2026-08-19 20:30:17 -04:00
Amaan Qureshi 14c158bbba generate: warn when a symbol is both a supertype and inlined
Currently, a symbol listed in both `supertypes` and `inline` produces
a phantom supertype entry in `node-types.json` for a rule that can
never appear in a tree. `intern_symbols` now drops the supertype with
a warning. A hard error would break 29 published grammars, including
python, go, ruby, rust, and scala, so that that decision is deferred
to the next breaking release. Fixes #5218
2026-08-19 20:30:17 -04:00
dependabot[bot] f235c2f1c3 build(deps): bump the cargo group with 4 updates
Bumps the cargo group with 4 updates: [bstr](https://github.com/BurntSushi/bstr), [cc](https://github.com/rust-lang/cc-rs), [clap_complete_nushell](https://github.com/clap-rs/clap) and [thiserror](https://github.com/dtolnay/thiserror).


Updates `bstr` from 1.13.0 to 1.13.1
- [Commits](https://github.com/BurntSushi/bstr/compare/1.13.0...1.13.1)

Updates `cc` from 1.4.2 to 1.4.3
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.4.2...cc-v1.4.3)

Updates `clap_complete_nushell` from 4.6.1 to 4.6.2
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete_nushell-v4.6.1...clap_complete_nushell-v4.6.2)

Updates `thiserror` from 2.0.19 to 2.0.20
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.19...2.0.20)

---
updated-dependencies:
- dependency-name: bstr
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: cc
  dependency-version: 1.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete_nushell
  dependency-version: 4.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: thiserror
  dependency-version: 2.0.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-18 10:39:56 +02:00
dependabot[bot] 95eaaa2130 build(deps): bump the npm group across 1 directory with 3 updates
Bumps the npm group with 3 updates in the /lib/binding_web directory: [esbuild](https://github.com/evanw/esbuild), [tsx](https://github.com/privatenumber/tsx) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `esbuild` from 0.28.1 to 0.28.2
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.28.1...v0.28.2)

Updates `tsx` from 4.23.11 to 4.23.12
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.23.11...v4.23.12)

Updates `typescript-eslint` from 8.66.0 to 8.67.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.28.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: tsx
  dependency-version: 4.23.12
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: typescript-eslint
  dependency-version: 8.67.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-18 10:37:31 +02:00
dependabot[bot] dff1fd868c build(deps): bump esbuild and vite in /lib/binding_web
Bumps [esbuild](https://github.com/evanw/esbuild) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite). These dependencies needed to be updated together.

Updates `esbuild` from 0.27.7 to 0.28.1
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.7...v0.28.1)

Updates `vite` from 7.3.2 to 7.3.6
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.6/packages/vite)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.28.1
  dependency-type: indirect
- dependency-name: vite
  dependency-version: 7.3.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-15 10:13:38 +02:00
Max Brunsfeld 0e2af0d8d1
Allow using Tree-sitter rust lib in multi-threaded web apps compiled to wasm32-unknown-unknown (#5851)
* feat(wasm): make syntax trees sendable

* test(wasm): transfer trees across workers

* test(wasm): use JSON grammar for tree transfer

* test(wasm): edit trees across workers

* test(wasm): share dlmalloc with tree-sitter

* test(wasm): simplify worker tree exchange

* test(wasm): drive tree exchange from Rust

* test(wasm): split sendable-tree xtask

* test(wasm): generalize Rust web fixture

* test(wasm): exercise parallel Rust tree access

* fix(wasm): use Rust global allocator for C core

* test(wasm): use default Rust allocator

* feat(wasm): support external scanners in Rust web apps

* Simplify example further, add a readme

* Regenerate wasm-stdlib

* Fix wasm_stdlib check script

* Vendor the Wasm standard library subset

* Test Unicode Ruby scanner behavior in Wasm

* Make Wasm tree languages instance-aware

* Test multi-threaded use of queries in wasm32-unknown

* Refactor reference-counted language storage

* Check ABI version compat before loading rest of language

* 🎨 Remove redundant #ifdef block

* Reject unsupported Rust Wasm builds on 0.26
2026-08-14 17:05:42 -07:00
Will Lillis 21e3614b8d perf(lib): fast path for ASCII characters in ts_lexer__advance
Yields a 0-5% improvement on parse speed (very grammar dependent).
2026-08-14 18:11:27 +02:00
Will Lillis 730946cfd5 perf(lib): fast path for ASCII characters in ts_lexer__get_lookahead
Yields a ~2% improvement on parse speed.
2026-08-14 18:11:27 +02:00
Christian Clason 277f53f886 build(swift)!: drop Package.swift
Problem: `Package.swift` build script is unmaintained and leads to build
errors.

Solution: Remove `Package.swift`; downstream tools should rely on the
swift-tree-sitter bindings (or, if they want a custom bare-metal build,
handle this in their own build scripts).
2026-08-14 12:51:38 +02:00
Christian Clason f8e735212b build(deps): bump binaryen to v132 2026-08-14 09:33:39 +02:00
Tim Vermeulen f30ee2b300 fix(parser): nest error children during recovery 2026-08-14 00:51:11 -04:00
Tim Vermeulen 869638f6cf fix(lib): accumulate error costs through hidden error nodes 2026-08-14 00:51:11 -04:00
Will Lillis c279989118 fix(xtask): check for vitest rather than chai and mocha
`test::run_wasm` gated dependency installation on `node_modules/chai` and
`node_modules/mocha`. Neither is a dependency (vitest is used instead),
so the check never passed and every `cargo xtask test-wasm`
reran `npm install`.
2026-08-14 00:13:54 -04:00
Will Lillis c68605f18d fix(rust)!: tie language string lifetimes to the proper containers
`Node::kind`, `Node::grammar_name`, the field name accessors,
`TreeCursor::field_name`, and the `Language` name lookups returned `&'static
str` while pointing into storage owned by the `TSLanguage`. Releasing the last
handle to a Wasm language frees that storage.

`Node` and `TreeCursor` now return `&'tree str`, which is ok because
`ts_tree_new` takes a reference to the language via `ts_language_copy` and
holds it until `ts_tree_delete`. The `Language` lookups return strings borrowed
from `&self`.
2026-08-14 00:13:54 -04:00
Will Lillis 7be497a14e fix(web)!: report null when lookahead iterator is not positioned
`currentType` and `currentTypeId` return `null` before the first iteration
step, after exhaustion, and after a reset.

`currentType` also falls back to the language's own name table instead of the
literal 'ERROR' when `Language.types` has no entry (auxiliary symbols). Other
bindings report `end` where this one reported 'ERROR'.
2026-08-14 00:13:54 -04:00
Will Lillis 081929092f fix(rust)!: make lookahead accessors fallible and iteration fused
`current_symbol` and `current_symbol_name` return an `Option` (`None` when the
iterator is not positioned on a symbol). Change `iter_names`'s item from
`&'static str` to `&str`.
2026-08-14 00:13:54 -04:00
Will Lillis 7bf4a10995 fix(lib)!: make lookahead iterator exhaustion sticky and retain its language
Track the iterator's phase so exhaustion is sticky, and gate the symbol name on
it, so `NULL` means "not positioned on a symbol".

`ts_lookahead_iterator_new` was also the only language consuming constructor
that did not `ts_language_copy`, so an iterator outliving a wasm language read
freed memory. Retain in `_new` and `_reset`, release in `_delete`.

Previously:
- `ts_lookahead_iterator__next` left a small parse state's cursor one past its
group end, so re-advancing an exhausted iterator resumed returning `true` and
walked through the rest of `ts_small_parse_table` and off the end of it.

- `ts_lookahead_iterator_current_symbol_name` returned `NULL` only when the
exhausted symbol index happened to fall outside the names table, so a grammar
with aliases returned a real but wrong name instead.
2026-08-14 00:13:54 -04:00
Will Lillis 721b211168 fix(generate)!: reject non-ASCII byte classes in (?-u:...) patterns
`(?-u:...)` switches `regex_syntax` to matching raw bytes, but the lexer
dispatches on decoded characters, so `expand_regex` converted the byte
class with a u8 cast. This is exact for ASCII bytes, and silently
misleading for anything aboove 0x80.

This is not reachable from grammar.js (node and QuickJS both reject
`(?-u:...)`). This change is to guard against future JS runtime changes,
as well as alternative frontends to the generate crate.
2026-08-14 00:13:39 -04:00
Will Lillis 2b15649f93 fix(generate): fold case-insensitive patterns at the AST leaves
A previous fix moved case folding for `/i` patterns out of `regex_syntax` and
into `expand_regex`, so folding could drop the two non-ASCII code points
Unicode simple folding maps onto ASCII letters: the long s `ſ` (U+017F)
onto `s`, and the Kelvin sign `K` (U+212A) onto `k`. Left in, they leak
into otherwise-ASCII tokens and stop those tokens from being extracted as
keywords.

By that point, though, the HIR has already turned a negated class into a
complement, so folding it applies the fold on the wrong side of the
negation. `(?i)[^a-z]` folds a set that contains `A-Z`, which re-admits
`a-z` and leaves a class matching very nearly everything.

`regex_syntax` folds each leaf of a class expression before applying that
leaf's negation and the set algebra above it. Keep that order and change
only the fold: walk the AST, replace each leaf with its fold, and translate
with `case_insensitive(false)`.
2026-08-12 23:40:32 -04:00
Jason Boatman 18cc71a6d9 feat(lib): add state to missing nodes.
This allows them to return possible missing values from a `LookaheadIterator`.
2026-08-12 23:39:44 -04:00
Will Lillis 816a2a0c15
refactor(rust): trim unused imports (#5845)
* Importing `char` brings deprecated methods into scope in some cases.
* Importing `str` isn't necessary in many other cases.
2026-08-12 14:27:52 +02:00
Will Lillis 42f33fe2f8 fix(query): correctly set a state's skipped_quantifier flag when a
zero quantifier skip is performed.

The zero-skip branch currently sets skipped_quantifier unconditionally.
That flag is correct only when the quantified step and its skip target
are _siblings_ at the same query depth. Otherwise, setting it allows for
a "leak" and disables unrelated, "outer" anchors.
2026-08-10 23:59:11 -05:00
dependabot[bot] 5c2533bb7b build(deps): bump the cargo group with 3 updates
Bumps the cargo group with 3 updates: [cc](https://github.com/rust-lang/cc-rs), [clap](https://github.com/clap-rs/clap) and [clap_complete](https://github.com/clap-rs/clap).


Updates `cc` from 1.4.0 to 1.4.1
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.4.0...cc-v1.4.1)

Updates `clap` from 4.6.5 to 4.6.6
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.5...clap_complete-v4.6.6)

Updates `clap_complete` from 4.6.8 to 4.6.9
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.8...clap_complete-v4.6.9)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-version: 4.6.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete
  dependency-version: 4.6.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-10 23:42:21 +02:00
dependabot[bot] 6ea9a7c031 build(deps): bump the npm group across 1 directory with 4 updates
Bumps the npm group with 4 updates in the /lib/binding_web directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [eslint](https://github.com/eslint/eslint), [tsx](https://github.com/privatenumber/tsx) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `@types/node` from 26.1.2 to 26.2.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `eslint` from 10.8.0 to 10.8.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.8.0...v10.8.1)

Updates `tsx` from 4.23.1 to 4.23.11
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.23.1...v4.23.11)

Updates `typescript-eslint` from 8.65.0 to 8.66.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.66.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 26.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: eslint
  dependency-version: 10.8.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: tsx
  dependency-version: 4.23.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: typescript-eslint
  dependency-version: 8.66.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-10 23:40:39 +02:00
Will Lillis 543734d286 feat(test): allow corpus tests to use cst outputs
This is already exposed for consumers via the CLI, and is a natural way
to express some test expectations over the sexp form.

Also clean up some repeated logic in the internal test code, and narrow
the cst rendering return type to `std::io::Result` rather than
`anyhow::Result`.
2026-08-09 15:48:37 -05:00
Will Lillis 5fae914f8f fix(query): correctly identify MISSING nodes in queries 2026-08-09 15:48:07 -05:00
Will Lillis 1b916ab754 ci: increase nvim-ts generate max jobs to 10 2026-08-09 12:28:50 -05:00
Will Lillis fad0a62a35 perf(generate): pool parse-table action lists
Every (state, terminal) parse-table entry stored its action list inline as a
32-byte `ParseTableEntry`, but across a grammar those lists are ~98-99% _duplicates_.
The number of distinct lists is a few thousand regardless of grammar size, while
total entries scale into the hundreds of thousands.

Store each unique action list once in a shared `ActionListPool` (a flat arena of
actions plus `(offset, len)` ranges) and replace the inline entry with a 4-byte
`ActionListId` (a pool index with the `reusable` flag packed into the high bit).

- intern_table converts the freshly built `ParseTable<ParseTableEntry>` into
  `ParseTable<ActionListId>`.
- minimize carries and operates on the 4-byte ids. The three global state
  renumberings rewrite Shift targets once at the pool level
  (`remap_terminal_references`), while the per-state unit-reduction redirects
  copy the changed list into a new slot (COW). `mark_fragile_tokens` becomes a
  free bit flip on the id.
- `canonicalize` dedups and compacts the pool once before `render`, dropping the
  dead and duplicate slots the remaps and COW leave behind. `render` assigns the
  output action-list offsets directly.

Yields ~7% wall time reduction, ~12% peak rss reduction.
2026-08-09 12:28:50 -05:00
Will Lillis dbcdd0416b perf(generate): shrink parse/lex/production ids from usize to u32
`ParseStateId`, `LexStateId`, `ProductionInfoId`, and `ReservedWordSetId`
each held a `usize`, while the runtime stores state/field ids as `u16` (so
`u32` has plenty of headroom). Shrinking them saves space for every parse-table
entry, and a number of other data structures.

Reduces wall time by 2-5%, peak rss by 9-12%.
2026-08-09 12:28:50 -05:00
Will Lillis 87d14ddfc6 generate: use the LexStateId typedef for lex state ids
Lex state ids were passed as bare usize in several spots. Route them
through the existing `LexStateId` typedef.
2026-08-09 12:28:50 -05:00
Will Lillis 142c60dca1 perf(generate): memoize auxiliary node info within each state
`get_auxiliary_node_info` scans every entry in the item set to collect the
non-auxiliary parents of a given auxiliary symbol. It was called once per
entry whose next symbol is auxiliary, and the same auxiliary symbol recurs
across many entries in a single state (a state's GOTO on a repeat symbol is
shared by every item advancing over it), so the same full scan was repeated
many times per state. Memoize the result per symbol within a single
`add_actions` call.

Reduces wall time 1-4%, rss flat.
2026-08-09 12:28:50 -05:00
Will Lillis b14413e4f4 perf(generate): inline single-action parse table entries
`ParseTableEntry` held a `Vec<ParseAction>` per entry. `ActionList` keeps up
to one action inline and spills to a Vec only for conflict entries.

Reduces walltime by >20%, peak rss by ~30%.
2026-08-09 12:28:50 -05:00
Will Lillis 2a81995058 perf(generate): memoize get_production_id
The production info depends only on the production, but was recomputed and
deduplicated via a linear deep-equality scan over production_infos for every
reduce item in every state. Cache `prod_id` -> `ProductionInfoId`.

Wall time reduction 0-3%, peak rss neutral.
2026-08-09 12:28:50 -05:00
Will Lillis d6ae19cc6d perf(generate): intern lookahead TokenSets
`ParseItemSetEntry` stores a `LookaheadSetId` into a `LookaheadSetPool` instead
of an owned `TokenSet`. Ids are canonical, so entry hash/eq/clone are integer
ops and state dedup stops walking set words. Unions and single-token inserts
are memoized by id, so the transitive closure and successor-kernel construction
stop re-materializing the same unions per state. Closure additions carry interned
ids with word-token membership precomputed.

For cpp, 3566 distinct lookahead sets back all 49,992 states. For ruby 2000 sets
for 91,991 states, and rust 646 for 16,796.

Reduces wall time by 10-15%, peak rss by 3-5%.
2026-08-09 12:28:50 -05:00
Will Lillis 6659ff57eb perf(generate): vectorize minimize's token_conflicts with precomputed bitsets
The state-merge pass's `token_conflicts` scanned every terminal entry of the
other state per non-shared token. Precompute flat, word-aligned bitsets,
`ConflictBits`, so each call is a handful of word ANDs.

Yields a 5-6% wall time reduction on large grammars, and flat to ~1%
loss on small grammers. Peak RSS unchanged.
2026-08-09 12:28:50 -05:00
Will Lillis b304ac9e56 perf(generate): stop duplicating kernel item sets in parse_state_info
Every state's kernel set was cloned into `parse_state_info` even though the
identical set already lives as the state-dedup map key at the same index
(state ids are the map's insertion order). `ParseStateInfo` now holds the
preceding-symbol sequences plus the dedup map moved out of the builder,
allowing the report path to read kernels via `get_index`. Drops the
duplicate kernel storage, allowing for a small reduction in wall time
and peak rss (1-3%).
2026-08-09 12:28:50 -05:00
Will Lillis 2ce7b409a8 perf(generate): replace coincident state lists with a without-word bitset
`CoincidentTokenIndex` kept a `Vec<ParseStateId>` per token pair whose
only user was keyword identification asking one question: "do all states
where a given pair coincides also allow the word token?" Track that
directly as a second bitset built in the same pass.

Reduces walltime by ~7% for small grammars (i.e. Go), and 20-25% for
larger grammars (i.e. Rust, cpp). Reduces peak rss by ~20% for smaller
grammars and ~40-50% for larger grammars.
2026-08-09 12:28:50 -05:00
Will Lillis 110a8becb8 perf(generate): shrink Symbol::index from usize to u32
This cuts `Symbol`'s size from 16 to 8 bytes. Results in a ~2-7% wall time
reduction, and ~7-10% peak RSS reduction.
2026-08-09 12:28:50 -05:00
Will Lillis fb2052f02d perf(generate): represent the grammar IR as a flat, pooled arena
Replace the owned `Rule`-tree grammar with a flat, pooled representation. `Rule`
nodes live in a `RulePool` arena and reference their children and params by
index (`RuleId`). Strings are interned in a `StrPool` and referred to by `StrId`.
Productions are stored as flat `ProductionStep`/`Production` slices indexed per
variable, rather than as nested vectors hanging off each `SyntaxVariable`. This
drops the per-rule heap allocation of the owned-tree form and is the groundwork
for the later table-building optimizations.

`parse_grammar` builds the pool, the prepare passes rewrite nodes in place, and
`prepare_grammar` returns a `PreparedGrammar` bundle that owns the run's `StrPool`
alongside the grammars. `build_tables`, `node_types`, and `render` borrow that
pool and resolve `StrId`s only at the output boundary.

Shows an average ~10% walltime reduction when combined with the previous
commit, with nearly all of the savings in `build_tables`. The `prepare`
stage is also ~70% faster and much more stable, but this contributes
much less to overall generate time. Improving early stages such as
`prepare` will be important for future interactive uses.
2026-08-09 12:28:50 -05:00
Will Lillis e72c8594fb perf(generate): precompute order-preserving item identity keys
Parse-item identity dominates parse-table construction. `ParseItem`'s
`Hash`, `Eq`, and `Ord` walk the entire production on every call,
comparing per-step precedence, aliases, and field names. These are
stored as strings for named precedences and for every alias and field.

Precompute, once per table build, two dense `u32` ids for every
`(production, dot)` slot by ranking the whole slot universe with the
item-content comparator, now extracted as `ItemContent`. Equal-content
slots are adjacent after sorting and the id increments only at content
boundaries, so comparing the `cmp` id reproduces the structural order
exactly (both an ordering and an equality). A second id, `eq_with_syms`,
refines `cmp` by the preceding symbols, which enter equality only for
items with `has_preceding_inherited_fields`.

`ParseItem` now carries its production's key slice, and `Hash`/`Eq`/`Ord`
become integer ops.

On the current string-based production storage this is performance-neutral
in isolation (the up-front ranking pays back the per-comparison savings).
It is the foundation for the following change to a pooled, integer-keyed
production representation, which makes both the ranking and the item
comparisons cheaper integer work.
2026-08-09 12:28:50 -05:00
Christian Clason 4f7ab22584 ci(release): add nightly builds 2026-08-08 19:09:56 +02:00
Newosko 308aee0c90 fix(lib): continue search for later named siblings in
`ts_tree_cursor_current_status`

By terminating early on `has_later_siblings`, `has_later_named_siblings`
was incorrectly reported as `false` in some cases. This led to the
execution of some queries to terminate early.

Also remove some dead branches inside `ts_tree_cursor_current_status`.

Co-authored-by: Will Lillis <will.lillis24@gmail.com>
2026-08-07 17:54:36 -04:00
Jason Boatman 003b10c280 feat(generate)!: expose OptLevel on generate_parser_for_grammar 2026-08-07 09:47:11 +02:00
Will Lillis 7511e3adaa fix(rust)!: tie query iterators to options lifetime
`QueryMatches` and `QueryCaptures` retain query cursor options while they are
being iterated. However, their types did not preserve the lifetime of the
options' progress callback, allowing the callback to be dropped while it
was still referenced by the cursor.

Add an explicit options lifetime to both iterator types and propagate it
through `matches_with_options` and `captures_with_options`. Use a static
options lifetime for iterators created without options.
2026-08-07 01:15:38 -04:00
Christian Clason 8966c69fcd build(deps): bump wasmtime-c-api to v36.0.13 2026-08-06 01:06:13 -04:00
dependabot[bot] 97e2eeb83c build(deps): bump brace-expansion in /crates/cli/eslint
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 5.0.8 to 5.0.9.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.8...v5.0.9)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 5.0.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-04 09:59:36 +02:00
dependabot[bot] fecce454cb build(deps): bump @types/node
Bumps the npm group with 1 update in the /lib/binding_web directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@types/node` from 26.1.1 to 26.1.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 26.1.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-04 09:38:07 +02:00
dependabot[bot] 36865d63bb build(deps): bump the cargo group with 2 updates
Bumps the cargo group with 2 updates: [clap](https://github.com/clap-rs/clap) and [webbrowser](https://github.com/amodm/webbrowser-rs).


Updates `clap` from 4.6.4 to 4.6.5
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.4...clap_complete-v4.6.5)

Updates `webbrowser` from 1.2.1 to 1.2.2
- [Release notes](https://github.com/amodm/webbrowser-rs/releases)
- [Changelog](https://github.com/amodm/webbrowser-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amodm/webbrowser-rs/compare/v1.2.1...v1.2.2)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.6.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: webbrowser
  dependency-version: 1.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-04 09:37:48 +02:00
Christian Clason 963b5a5a97 build(deps): bump rquickjs to v0.12.2 2026-07-29 02:21:32 +02:00
dependabot[bot] 4deef2d5ef build(deps): bump eslint
Bumps the npm group with 1 update in the /lib/binding_web directory: [eslint](https://github.com/eslint/eslint).


Updates `eslint` from 10.7.0 to 10.8.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.7.0...v10.8.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 10.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-28 00:29:36 +02:00
dependabot[bot] 049dee2634 build(deps): bump the cargo group with 6 updates
Bumps the cargo group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.103` | `1.0.104` |
| [clap](https://github.com/clap-rs/clap) | `4.6.3` | `4.6.4` |
| [glob](https://github.com/rust-lang/glob) | `0.3.3` | `0.3.4` |
| [serde](https://github.com/serde-rs/serde) | `1.0.228` | `1.0.229` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.150` | `1.0.151` |
| [thiserror](https://github.com/dtolnay/thiserror) | `2.0.18` | `2.0.19` |


Updates `anyhow` from 1.0.103 to 1.0.104
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.103...1.0.104)

Updates `clap` from 4.6.3 to 4.6.4
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.3...clap_complete-v4.6.4)

Updates `glob` from 0.3.3 to 0.3.4
- [Release notes](https://github.com/rust-lang/glob/releases)
- [Changelog](https://github.com/rust-lang/glob/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/glob/compare/v0.3.3...v0.3.4)

Updates `serde` from 1.0.228 to 1.0.229
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.228...v1.0.229)

Updates `serde_json` from 1.0.150 to 1.0.151
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.150...v1.0.151)

Updates `thiserror` from 2.0.18 to 2.0.19
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.18...2.0.19)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.104
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-version: 4.6.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: glob
  dependency-version: 0.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: serde
  dependency-version: 1.0.229
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: serde_json
  dependency-version: 1.0.151
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: thiserror
  dependency-version: 2.0.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-27 23:49:19 +02:00
Sjoerd Langkemper 0900f84eab fix(generate): honor right associativity despite a lower-precedence shift
A SHIFT/REDUCE conflict can bundle several shift interpretations with
different precedences against a single reduce. `handle_conflict` weighed them
with only `shift_is_less` and `shift_is_more`, so a lone lower-precedence
shift set `shift_is_less` and the REDUCE won outright, even when another
interpretation tied the REDUCE in precedence and the REDUCE was declared
right-associative. That tie's associativity should have won by shifting, so a
right-associative rule silently became left-associative as soon as an
unrelated lower-precedence rule was added to the grammar.

Track the equal-precedence case explicitly, and in the reduce-wins branch
shift instead when a tying interpretation exists and the reduce actions are
purely right-associative.

Co-authored-by: Will Lillis <will.lillis24@gmail.com>
2026-07-27 02:06:35 -04:00
Christian Clason 4d0f126996 test(web): fix new lint failure 2026-07-25 18:02:19 +02:00
dependabot[bot] ee3fa37bc3 build(deps): bump the npm group across 1 directory with 7 updates
Bumps the npm group with 7 updates in the /lib/binding_web directory:

| Package | From | To |
| --- | --- | --- |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.5.2` | `26.1.1` |
| [dts-buddy](https://github.com/sveltejs/dts-buddy) | `0.7.0` | `0.8.3` |
| [esbuild](https://github.com/evanw/esbuild) | `0.27.7` | `0.28.1` |
| [eslint](https://github.com/eslint/eslint) | `9.39.4` | `10.7.0` |
| [source-map](https://github.com/mozilla/source-map) | `0.7.6` | `0.8.0` |
| [tsx](https://github.com/privatenumber/tsx) | `4.21.0` | `4.23.1` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.58.0` | `8.65.0` |



Updates `@types/node` from 25.5.2 to 26.1.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `dts-buddy` from 0.7.0 to 0.8.3
- [Release notes](https://github.com/sveltejs/dts-buddy/releases)
- [Changelog](https://github.com/sveltejs/dts-buddy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/dts-buddy/compare/v0.7.0...v0.8.3)

Updates `esbuild` from 0.27.7 to 0.28.1
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.7...v0.28.1)

Updates `eslint` from 9.39.4 to 10.7.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.39.4...v10.7.0)

Updates `source-map` from 0.7.6 to 0.8.0
- [Release notes](https://github.com/mozilla/source-map/releases)
- [Changelog](https://github.com/mozilla/source-map/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mozilla/source-map/compare/0.7.6...v0.8.0)

Updates `tsx` from 4.21.0 to 4.23.1
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.21.0...v4.23.1)

Updates `typescript-eslint` from 8.58.0 to 8.65.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.65.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 26.1.1
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm
- dependency-name: dts-buddy
  dependency-version: 0.8.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: esbuild
  dependency-version: 0.28.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: eslint
  dependency-version: 10.7.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm
- dependency-name: source-map
  dependency-version: 0.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: tsx
  dependency-version: 4.23.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: typescript-eslint
  dependency-version: 8.65.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-25 18:02:19 +02:00
Will Lillis 15ea3328e1 fix(parser): restart recovery for invalid tokens in the error state
The early `ts_parser__recover` dispatch for `ERROR_STATE` in
`ts_parser__advance` was dropped in `201b41cf1`. Without it, tokens
with no valid action re-enter `ts_parser__handle_error` per token
and fragment the tree instead of extending one ERROR node. Restore
it to recover the old (pre-0.25) error recovery behavior.
2026-07-25 18:01:29 +02:00
dependabot[bot] 86e1fedfaf build(deps): bump postcss from 8.5.12 to 8.5.23 in /lib/binding_web
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.12 to 8.5.23.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.12...8.5.23)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.23
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-25 17:56:46 +02:00
dependabot[bot] 4777d410dd build(deps): bump brace-expansion in /crates/cli/eslint
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 5.0.7 to 5.0.8.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.7...v5.0.8)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 5.0.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-25 17:56:30 +02:00
dependabot[bot] 5d232ff77c build(deps): bump js-yaml from 4.2.0 to 4.3.0 in /lib/binding_web
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.2.0 to 4.3.0.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.2.0...4.3.0)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.3.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-25 17:42:33 +02:00
dependabot[bot] 9beff8a998 build(deps): bump brace-expansion in /crates/cli/eslint
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 5.0.6 to 5.0.7.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.6...v5.0.7)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 5.0.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-25 17:42:03 +02:00
Will Lillis 3a76a8c8ca feat(cli)!: rework HTML output modes in highlight
- `--layout <document|line-numbers|fragment>` (default `document`): the
  document structure. `document` is a self-contained page wrapping a plain
  `<div class="highlight"><pre><code>` block, `line-numbers` adds a line-number
  `<table>`, and `fragment` emits only the code markup with no surrounding
  `<head>`/`<style>`/`<body>` so it can be embedded in an existing page.

- `--style <classes|inline|minimal>` (default `classes`): how token colors
  are applied. `classes` uses `class="..."` spans plus a generated `<style>`,
  `inline` bakes colors onto each span so the markup is self-contained, and
  `minimal` emits bare classes with no colors (bring your own stylesheet).

Deprecates the `--css-classes` flag.
2026-07-24 20:56:56 -04:00
Will Lillis d205dc92de fix(rust)!: require parser loggers to be Send + 'static
`Parser::set_logger` boxes the logger callback into a type-erased C
pointer, but `Parser` carries no lifetime parameter. This allows for
two kinds of UB:

- Use-after-free: a logger could capture a non-`'static` borrow, let the
  borrowed value drop, and then dangle the next time the parser logged.
- Data race: a logger could capture a `!Send` value such as an `Rc`. The
  parser could then be moved to another thread and logged from there
  while the original thread still held a clone, racing on the reference
  count.

As a fix, we just require that logger is `Send + 'static`. The
alternative here is to attach a lifetime parameter to `Parser`, but this
is highly breaking for what is mostly a debugging utility. As an escape
hatch `set_logger_unchecked` is added to the public API as an `unsafe fn`
to correctly communicate the risks and invariants that must be held.
2026-07-24 20:56:16 -04:00
Alex Le Blanc 65b5b03c94
fix(highlight): use std::sync::OnceCell for various loader fields
`std::unsync::OnceCell` allows for data races and requires an (incorrect) manual impl of the `Sync` trait.
2026-07-23 18:30:15 -04:00
dependabot[bot] 0a0e77c375 ci: bump the actions group with 3 updates
Bumps the actions group with 3 updates: [actions/checkout](https://github.com/actions/checkout), [actions/setup-node](https://github.com/actions/setup-node) and [vmactions/omnios-vm](https://github.com/vmactions/omnios-vm).


Updates `actions/checkout` from 7.0.0 to 7.0.1
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v7.0.0...v7.0.1)

Updates `actions/setup-node` from 6.4.0 to 7.0.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6.4.0...v7.0.0)

Updates `vmactions/omnios-vm` from 1.3.3 to 1.3.4
- [Release notes](https://github.com/vmactions/omnios-vm/releases)
- [Commits](https://github.com/vmactions/omnios-vm/compare/v1.3.3...v1.3.4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: actions/setup-node
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: vmactions/omnios-vm
  dependency-version: 1.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-20 23:46:28 +02:00
dependabot[bot] 5e37a97684 build(deps): bump the cargo group with 4 updates
Bumps the cargo group with 4 updates: [cc](https://github.com/rust-lang/cc-rs), [clap](https://github.com/clap-rs/clap), [clap_complete_nushell](https://github.com/clap-rs/clap) and [bitflags](https://github.com/bitflags/bitflags).


Updates `cc` from 1.2.66 to 1.2.67
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.66...cc-v1.2.67)

Updates `clap` from 4.6.1 to 4.6.2
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.1...clap_complete-v4.6.2)

Updates `clap_complete_nushell` from 4.6.0 to 4.6.1
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete_nushell-v4.6.0...clap_complete_nushell-v4.6.1)

Updates `bitflags` from 2.13.0 to 2.13.1
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/2.13.0...2.13.1)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.67
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-version: 4.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete_nushell
  dependency-version: 4.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: bitflags
  dependency-version: 2.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-20 23:39:49 +02:00
Will Lillis f45a488dea feat(cli)!: make parent flags a hard requirements via clap
Also correct a parameter name in `print_tree_graph`
2026-07-18 17:00:53 -04:00
Will Lillis 78481a8dfc fix(init): don't lowercase repository url 2026-07-18 15:35:49 -04:00
Will Lillis 1ffd612be5 fix(query): transfer a leading boundary anchor across a zero-matched quantifier
`(P . Q* Y)` with zero `Q` dropped the leading `.`, so `Y` matched at any
position instead of being pinned to the parent's first named child. On a `?`/`*`
zero-skip, when the skipped step is the parent's first child and carries a
leading anchor, transfer that first-child requirement to the skip target.
2026-07-17 17:53:45 -04:00
Will Lillis dfcf73921c fix(query): keep the trailing anchor when a zero-matched quantifier is anchored
on both sides

In `A . Q* . B`, a zero-matched `Q` made both anchors vacuous, so `A` and `B`
were no longer required to be immediate siblings. Only relax the following
anchor on a `?`/`*` zero-skip when the skipped step has no leading anchor of
its own; otherwise the adjacency transfers through the empty run.
2026-07-17 17:53:45 -04:00
Philipp 8fe4f218fb
fix(cli): init --update should not update setup.py after replacing it (#5760) 2026-07-17 17:53:14 -04:00
Christian Clason 8b7c213443 build(deps): bump binaryen to v131 2026-07-17 02:16:46 -04:00
Will Lillis 0e76a0bd53 fix(generate): fold case-insensitive patterns ourselves
Unicode simple case folding maps two non-ASCII code points onto ASCII
letters: the long s `ſ` (U+017F) onto `s`, and the Kelvin sign `K` (U+212A)
onto `k`. So `regex_syntax` pulls them into any case-insensitive pattern,
which is virtually never intended and has two bad effects:

  * such tokens can no longer be extracted as keywords, because they are not
    a subset of an ASCII `word` token (#5607)
  * a broad class like `[^"]` or `\p{L}` carrying `/i` loses `ſ`/`K`, even
    though it legitimately contains them (#5755)

Rather than let `regex_syntax` fold, parse patterns unfolded and fold them
ourselves in `case_fold_ascii_safe`: fold via `regex_syntax`, then drop
`ſ`/`K` only when folding introduced them (they were not already in the
base set). A class that already contains them keeps them.
2026-07-16 21:32:24 -04:00
Kamal Chandra Upreti 49dd7b4079
docs(init): fix broken link 2026-07-15 12:13:07 +00:00
Will Lillis b23c9fceb7 feat(tags)!: include underlying LanguageError in tag
crate's error enum
2026-07-14 19:19:49 -04:00
Will Lillis db078ba439 feat(highlight)!: include underlying LanguageError in highlight
crate's error enum
2026-07-14 19:19:49 -04:00
dependabot[bot] da2838c39c build(deps): bump memchr from 2.8.2 to 2.8.3 in the cargo group
Bumps the cargo group with 1 update: [memchr](https://github.com/BurntSushi/memchr).


Updates `memchr` from 2.8.2 to 2.8.3
- [Commits](https://github.com/BurntSushi/memchr/compare/2.8.2...2.8.3)

---
updated-dependencies:
- dependency-name: memchr
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-13 23:47:28 +02:00
Will Lillis d11d18f746 feat(web): expand Language::load to accept URL filepath
Some checks failed
CI / check-wasm-stdlib (push) Has been cancelled
CI / checks (push) Has been cancelled
CI / sanitize (push) Has been cancelled
CI / build (push) Has been cancelled
2026-07-11 12:56:21 -04:00
Will Lillis ee0847d605 fix(ci): re-set executable permission on release artifacts before zipping
Some checks are pending
CI / checks (push) Waiting to run
CI / sanitize (push) Waiting to run
CI / build (push) Waiting to run
CI / check-wasm-stdlib (push) Waiting to run
2026-07-11 10:25:33 +02:00
ObserverOfTime 08fcb7f2ef fix(templates): replace deprecated method in Package.swift
Some checks are pending
CI / checks (push) Waiting to run
CI / sanitize (push) Waiting to run
CI / build (push) Waiting to run
CI / check-wasm-stdlib (push) Waiting to run
2026-07-11 00:50:56 -04:00
ObserverOfTime 805a7cd0ae fix(templates): add C source files to Python sdist 2026-07-11 00:50:56 -04:00
Andrew Mayo d2193f4dba docs(comment): misleading/wrong comment stating that initial state TSStateId is 0 rather than 1 2026-07-11 00:49:54 -04:00
Will Lillis 07c4ed220e fix(generate): strip non-ASCII case folds
Regexes with the case-insensitive 'i' flag caused unicode simple case folding,
which maps two non-ASCII code points onto ASCII letters:

- `ſ` (U+017F) onto `s`
- the Kelvin sign `K` (U+212A) onto `k`

This pushed such tokens outside an ASCII `word` token, so they failed to
extract as keywords.
2026-07-11 00:46:53 -04:00
Will Lillis 7027979c2b feat(cli)!: don't load all languages via config when lib path is passed
Also, make `--lang-name` without `--lib-path` a hard error
2026-07-11 00:27:13 -04:00
Will Lillis 17a02fef5e fix(rust): address new clippy lints 2026-07-11 00:03:44 -04:00
Julia Hansbrough cc7be1fd47
fix(templates): generated array macros do not compile with C++
Some checks failed
CI / checks (push) Has been cancelled
CI / sanitize (push) Has been cancelled
CI / build (push) Has been cancelled
CI / check-wasm-stdlib (push) Has been cancelled
Deploy Docs / deploy-docs (push) Has been cancelled
Problem: A set of `array_*` macros (`array_push`, `array_extend`, etc) implicitly convert a `void*` into a different pointer type.  In environments that compile these headers as C++, this implicit conversion is an error.

Solution: This commit adds an `_array_cast` macro that uses `decltype` to cast the `void*` to the proper type when compiling as C++.
2026-07-08 08:38:37 +00:00
Will Lillis cce7768b27 docs: specify valid children of supertype nodes 2026-07-08 10:36:50 +02:00
Will Lillis 15da6ddf12 feat(generate): improve error message for supertypes made invalid by
hidden children
2026-07-08 10:36:50 +02:00
Christian Clason e470c32c24 build(deps): bump rquickjs to v0.12.1 2026-07-08 10:35:41 +02:00
dependabot[bot] a1f55783e6 ci: bump korthout/backport-action in the actions group
Some checks failed
CI / checks (push) Has been cancelled
CI / sanitize (push) Has been cancelled
CI / build (push) Has been cancelled
CI / check-wasm-stdlib (push) Has been cancelled
Bumps the actions group with 1 update: [korthout/backport-action](https://github.com/korthout/backport-action).


Updates `korthout/backport-action` from 4.5.2 to 4.6.0
- [Release notes](https://github.com/korthout/backport-action/releases)
- [Commits](https://github.com/korthout/backport-action/compare/v4.5.2...v4.6.0)

---
updated-dependencies:
- dependency-name: korthout/backport-action
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-07 00:33:16 +02:00
dependabot[bot] bf6f829c83 build(deps): bump the cargo group with 3 updates
Bumps the cargo group with 3 updates: [clap_complete](https://github.com/clap-rs/clap), [rand](https://github.com/rust-random/rand) and [rustc-hash](https://github.com/rust-lang/rustc-hash).


Updates `clap_complete` from 4.6.5 to 4.6.7
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.5...clap_complete-v4.6.7)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `rustc-hash` from 2.1.2 to 2.1.3
- [Changelog](https://github.com/rust-lang/rustc-hash/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/rustc-hash/compare/v2.1.2...v2.1.3)

---
updated-dependencies:
- dependency-name: clap_complete
  dependency-version: 4.6.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: rand
  dependency-version: 0.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: rustc-hash
  dependency-version: 2.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-07 00:06:55 +02:00
Will Lillis 9fc2f486a8 docs(queries): clarify behavior of first child anchor example
Some checks failed
Check Bindgen Output / check-bindgen (push) Has been cancelled
CI / checks (push) Has been cancelled
CI / sanitize (push) Has been cancelled
CI / build (push) Has been cancelled
CI / check-wasm-stdlib (push) Has been cancelled
Deploy Docs / deploy-docs (push) Has been cancelled
Check Wasm Exports / check-wasm-exports (push) Has been cancelled
2026-07-01 19:31:05 -04:00
Will Lillis ec872bca82 docs(queries): specify anchor behavior with quantifiers and groups
- An anchor between two patterns is vacuous when an adjacent quantifier
matches zero

- A leading or trailing anchor on a node applies to the nearest matched node
when an adjacent optional is skipped

- An anchor at the edge of a group or alternation is not allowed.
2026-07-01 19:31:05 -04:00
Will Lillis 139b801cce fix(query): apply a trailing anchor when its optional node is skipped
A trailing `.` after an optional node, e.g. `(p (a)+ @a . (b)? @b .)`, sets
`is_last_child` on the `(b)?` step. When `(b)?` matched zero, the zero-skip
jumped past that step to completion, so the last-child requirement was never
enforced.

When a zero-skip would bypass a step carrying `is_last_child`, require that
the last matched node really is the last named child. Gated on the
`alternative_is_skip` edge marker.
2026-07-01 19:31:05 -04:00
Will Lillis a6bc724748 fix(query): make an anchor after a zero-matched quantifier vacuous
A `.` anchor between a quantified pattern and a following node, e.g.
`(parent (comment)* @c . (decl))`, was treated as a leading anchor when the
quantifier matched zero. The zero-skip jumps a state directly onto the anchored
step, where `is_immediate` was enforced even though no sibling had matched before
it. Name that skip edge (`alternative_is_skip`) and, when a state follows it,
record that it skipped the quantifier (`skipped_quantifier`). The
immediate anchor is then supressoed for that state's next match.
2026-07-01 19:31:05 -04:00
Will Lillis b69d00b0fe fix(query): forbid an anchor at the end of a group
A `.` at the end of a group, such as `((comment)+ @c .)`, was silently dropped
during compilation. Reject rather than dropping.
2026-07-01 19:31:05 -04:00
Will Lillis 4915d1bd09 feat(query): enhanced step dumper
Extract the step dump into `ts_query__dump_steps` and enrich it with the control-flow
fields and analysis annotations.
2026-07-01 19:31:05 -04:00
Will Lillis 91fd04f96a fix(query): short-circuit longest-match dedup for disjoint states
An unanchored quantified sibling like `(program (comment)+ @doc (class))`
keeps one match state per matching sibling. A recent fix stopped over-pruning
them, but the per-node longest-match dedup is pairwise, so with n live states
matching became O(n^3).

Two states can only be capture subsets of one another if their captured
byte ranges overlap, so keep each group ordered by first-capture position
and stop the pairwise scan once the rest of the group is disjoint.

Refs neovim/neovim#40517
2026-07-01 18:07:14 -04:00
Will Lillis 57ea5340c6 fix(xtask): eliminate silent errors in zig fetch 2026-06-30 19:37:55 -04:00
dependabot[bot] 5b591babd1 ci: bump actions/cache from 5 to 6 in the actions group
Bumps the actions group with 1 update: [actions/cache](https://github.com/actions/cache).


Updates `actions/cache` from 5 to 6
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-29 23:39:20 +02:00
Christian Clason ae2081989e fix(deps): zig manifest for wasmtime is missing windows hashes
Problem: `zig fetch` on 0.16 can't download zip archives, which wasmtime
uses for Windows. This makes `cargo xtask upgrade-wastime` fail silently
with empty hashes for these platforms.

Solution: Re-run xtask with Zig 0.17 nightly.
2026-06-29 23:18:32 +02:00
Will Lillis 41d3e16eac test(query): regression test for anchored siblings inside a parent 2026-06-28 13:43:53 +02:00
Will Lillis 98c25b9aa0 feat(rust)!: change ts_set_allocator to accept one optional Allocator
object rather than 4 separate optional function pointers.

Helps prevent misuse via mixing different allocators. Also update the
doc comment with relevant safety information.
2026-06-27 23:22:11 -04:00
Will Lillis aec288f83c feat(generate)!: collect warnings as structured data
Allows for use by library consumers, CLI users when the `--json` flag is
passed, and cleans up core logic.
2026-06-27 22:39:24 -04:00
Michael Davis 9991c0f46f Add ts_query_copy for cloning queries
This allows duplicating a query so that it can be modified by using
disable_capture or disable_pattern, without re-parsing the query. The
new `ts_query_copy` creates a deep copy of the entire query object.

A motivation for this is tags.scm code navigation queries. You might
want to have one version of the compiled query capture only definitions
and the other only capture references, since references are much much
more common than definitions in a typical codebase. Instead of
re-parsing and analyzing the query and then calling
`ts_query_disable_capture`, we can clone the built query all-at-once and
then disable captures / patterns.
2026-06-27 17:53:13 -04:00
Lucas M. de Jong Larrarte 99bceec689 fix(query): skip passthrough steps when checking for fallible step splitting
Problem: In queries matching a parent node with anchored children
(siblings) where the first sibling has a quantifier and is not
captured, the check for fallible steps skips splitting the state because
the next node is a passthrough node (and not an is_immediate one). This
prevents the query from matching beyond the first occurrence.

Solution: In the check for fallible steps, skip the next steps if they
are passthrough steps.
2026-06-27 23:44:02 +02:00
Lucas M. de Jong Larrarte 8b43f42dca fix(query): avoid overriding states not seeking immediate match with states seeking immediate match
Problem: In queries matching a parent node with anchored children
(siblings) where the first anchored sibling has a quantifier, the
deduplication logic for states is overly aggressive. The logic causes
the state split on the first capture (with the parent) to be dropped in
favor of the loopback state. This results in the query not being able to
match beyond the first occurrence.

Solution: Prevent the deduplication logic from dropping a state that is
not seeking an immediate match for one that is seeking an immediate
match, since the one not seeking for an immediate match could still
match later nodes.
2026-06-27 23:44:02 +02:00
Lucas M. de Jong Larrarte 1b110f62dd fix(query): take anchors between siblings into account for fallible step splitting
Problem: In queries matching a parent node with anchored children
(siblings), the check for fallible steps only considers nodes with
children, which results in no state split being made for anchored
siblings (node1) . (node2). This prevents the query from matching beyond
the first occurrence.

Solution: Make the check for fallible steps consider also the case where
the next step is at the same depth and must be matched immediately
after.
2026-06-27 23:44:02 +02:00
Christian Clason cdae49be40 ci: update workflows for breaking actions/checkout change
Problem: actions/checkout@v7 refuses to checkout fork pull request code
on `pull_request_target` trigger, breaking backport and reviewers-remove
workflows on external PRs.

Solution:
* backport: Checkout the merged-into base branch by name.
* reviewers-remove: inline script so no checkout needed.
2026-06-27 23:25:18 +02:00
Andrew Gibb cbee467266
docs(readme): add link for cargo-binstall
* Update cli README.md

Added a link to CLI readme to make it clear to an outsider that
cargo-binstall is a different tool than cargo.

* Apply suggestion from @WillLillis

Co-authored-by: Will Lillis <will.lillis24@gmail.com>

---------

Co-authored-by: Christian Clason <ch.clason+github@icloud.com>
Co-authored-by: Will Lillis <will.lillis24@gmail.com>
2026-06-26 08:10:43 -04:00
Christian Clason 8668f2c74f build(deps): bump wasmtime-c-api to v36.0.12 2026-06-26 14:07:35 +02:00
Christian Clason cc85df9440 build(deps): bump wasmtime-c-api to v36.0.11 2026-06-25 22:54:45 -04:00
ALancki1 b360d655cc Clarify behavior of advance(..., skip) in external scanner docs
Clarifies the behavior of `TSLexer::advance` when `skip=true`.

The previous documentation didn't make it clear that `skip=true` should only be used before a token starts. Using it after `mark_end` can affect the token’s starting position and lead to incorrect or zero-length ranges.

Fixes #2315
2026-06-25 22:54:18 -04:00
Will Lillis a8338eccef fix(dsl): forbid invalid field names 2026-06-24 17:38:21 -04:00
dependabot[bot] 68316916e0 ci: bump the actions group with 2 updates
Bumps the actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [vmactions/omnios-vm](https://github.com/vmactions/omnios-vm).


Updates `actions/checkout` from 6.0.3 to 7.0.0
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6.0.3...v7.0.0)

Updates `vmactions/omnios-vm` from 1.3.2 to 1.3.3
- [Release notes](https://github.com/vmactions/omnios-vm/releases)
- [Commits](https://github.com/vmactions/omnios-vm/compare/v1.3.2...v1.3.3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: vmactions/omnios-vm
  dependency-version: 1.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-23 00:04:47 +02:00
dependabot[bot] 29e38c284e build(deps): bump cc from 1.2.64 to 1.2.65 in the cargo group
Bumps the cargo group with 1 update: [cc](https://github.com/rust-lang/cc-rs).


Updates `cc` from 1.2.64 to 1.2.65
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.64...cc-v1.2.65)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.65
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-22 23:46:36 +02:00
Will Lillis d861eab22b fix(build): use std helper rather than passing -std=c11 flag
MSVC expects `/std:c11`
2026-06-16 23:55:43 -04:00
Will Lillis 6b7e298549 fix(lib): address strict aliasing violations in TreeCursor type 2026-06-16 23:55:43 -04:00
dependabot[bot] 04bac19343 ci: bump vmactions/omnios-vm from 1 to 1.3.2 in the actions group
Bumps the actions group with 1 update: [vmactions/omnios-vm](https://github.com/vmactions/omnios-vm).


Updates `vmactions/omnios-vm` from 1 to 1.3.2
- [Release notes](https://github.com/vmactions/omnios-vm/releases)
- [Commits](https://github.com/vmactions/omnios-vm/compare/v1...v1.3.2)

---
updated-dependencies:
- dependency-name: vmactions/omnios-vm
  dependency-version: 1.3.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-16 10:40:26 +02:00
dependabot[bot] 6ad626c686 build(deps): bump js-yaml from 4.1.1 to 4.2.0 in /lib/binding_web
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.1 to 4.2.0.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/commits)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.2.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-16 10:39:58 +02:00
Christian Clason d3314cb62e build(deps): bump rquickjs to v0.12.0 2026-06-15 18:49:48 -04:00
dependabot[bot] d77f764dcd build(deps): bump the cargo group with 2 updates
Bumps the cargo group with 2 updates: [cc](https://github.com/rust-lang/cc-rs) and [memchr](https://github.com/BurntSushi/memchr).


Updates `cc` from 1.2.63 to 1.2.64
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.63...cc-v1.2.64)

Updates `memchr` from 2.8.1 to 2.8.2
- [Commits](https://github.com/BurntSushi/memchr/compare/2.8.1...2.8.2)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.64
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: memchr
  dependency-version: 2.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-15 23:44:16 +02:00
buckynbrocko 49f5027158 Added tree-sitter test option --show-diff-markers to force use of + & - while still coloring output
Also cleanup the trailing newline special casing in the `TestDiff`
`Display` impl.

Co-authored-by: Will Lillis <will.lillis24@gmail.com>
2026-06-13 10:29:37 -04:00
Will Lillis 3ed12db8f0 fix(rust): use more accurate types for various IoError structs 2026-06-13 09:35:12 -04:00
Christian Clason de4b622223 build(deps): bump wasmtime-c-api to v36.0.10 2026-06-10 17:36:02 -04:00
Christian Clason 2fb79a4325 build(deps): bump binaryen to v130 2026-06-10 17:25:15 -04:00
jannschu 519d511488 fix(cli): highlight test did not properly check for spans on later rows
The early exit condition to skip remaining highlights was incorrectly
checking the lexicographical order, which could lead to false
passes if a highlight was on a later row with smaller column number.
2026-06-08 18:09:03 -04:00
dependabot[bot] 811b5308f8 ci: bump actions/checkout from 6.0.2 to 6.0.3 in the actions group
Bumps the actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 6.0.2 to 6.0.3
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6.0.2...v6.0.3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-08 23:50:59 +02:00
Will Lillis 4455da7102 fix(rust): more clippy fixes in generate
Additional clippy lints fire when certain modules are temporarily made
public, i.e. for testing purposes. Fixing these reduces clutter and
helps with internal development.
2026-06-05 17:31:40 -04:00
Will Lillis 4154ff23c0 fix(docs): explicitly warn against aliasing supertypes 2026-06-05 17:31:26 -04:00
Will Lillis d10b7272d2 fix(cli): warn user for various malformed tests
- attribute on same line of name
- typo'd attribute
- no closing `=====` line after the test body
2026-06-05 17:31:09 -04:00
Will Lillis d9acc99734 fix(cli): display warning to user if parser test corpus dir isn't found 2026-06-05 17:30:52 -04:00
Will Lillis 0617a59ed6 fix: add DESCRIPTION to grammar Makefile template 2026-06-05 09:43:49 +02:00
dependabot[bot] 5742052982 build(deps): bump the cargo group with 3 updates
Bumps the cargo group with 3 updates: [cc](https://github.com/rust-lang/cc-rs), [log](https://github.com/rust-lang/log) and [memchr](https://github.com/BurntSushi/memchr).


Updates `cc` from 1.2.62 to 1.2.63
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.62...cc-v1.2.63)

Updates `log` from 0.4.29 to 0.4.30
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.29...0.4.30)

Updates `memchr` from 2.8.0 to 2.8.1
- [Commits](https://github.com/BurntSushi/memchr/compare/2.8.0...2.8.1)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.63
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: log
  dependency-version: 0.4.30
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: memchr
  dependency-version: 2.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-03 12:06:15 +02:00
dependabot[bot] 94d419832e ci: bump actions/checkout from 6 to 6.0.2 in the actions group
Bumps the actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 6 to 6.0.2
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v6.0.2)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-03 12:05:42 +02:00
Trevor Burnham ff4dbc0589
perf(bindings): defer importlib.resources import in python template (#5589)
Co-authored-by: ObserverOfTime <chronobserver@disroot.org>
2026-06-01 23:08:00 -04:00
Will Lillis 15ddfb21ed fix(lib): Consider subtree lookahead bytes when determining whether the
parser can reuse a node.

Lookahead bytes can be used to decide what a node is parsed as, so it's
resaonable to consider this as part of a node's "range" when deciding
which edits affect it.
2026-06-01 18:12:56 -04:00
jannschu f541fec5ad fix(cli): infinite loop in highlight test 2026-05-31 12:37:38 -04:00
Will Lillis cd338a7a43 fix(loader): skip --no-undefined for sanitizer grammar builds
Compiling a grammar with `-fsanitize=address` (or any other sanitizer)
emits a module constructor in the parser object file that references
runtime symbols like `__asan_init`. These runtime-resolved symbols are
incompatible with `-Wl,--no-undefined`, which breaking sanitized test
runs on clang. (gcc happens to paper over this by auto-linking libasan
into the .so)

To fix, detect `-fsanitize=` in the compile flags and omit `--no-undefined`
in that case.
2026-05-31 10:56:40 -04:00
Will Lillis ceef7d179f fix(lib): rewrite ts_subtree__write_to_string to be iterative 2026-05-31 10:56:12 -04:00
Will Lillis 797d2bddc4 fix(cli): make check_consistent_sizes iterative
The recursive helper could overflow with deeply nested trees.
2026-05-31 10:56:12 -04:00
Will Lillis e00bf7bc84 fix(cli): print fuzzing seed unconditionally 2026-05-31 10:56:12 -04:00
Will Lillis f1db12c31c fix(cli): improve soundness of cst range calculation 2026-05-31 10:55:47 -04:00
Will Lillis 4d362b4b98 feat(generate): derive Deserialize on GenerateError and all subtypes
These types all derive `Serialize` already.
2026-05-31 08:45:11 +02:00
Georges Savoundararadj 1da46327b3 fix(lib): use correct TREE_SITTER_HIDE_SYMBOLS macro name in alloc.h
The alloc.h header checked for TREE_SITTER_HIDDEN_SYMBOLS, but the
canonical macro name used everywhere else (api.h, render.rs, setup.py)
is TREE_SITTER_HIDE_SYMBOLS. This mismatch meant that defining
TREE_SITTER_HIDE_SYMBOLS (as the Python binding build does) would not
actually hide the allocator symbols in alloc.h.

Fixes tree-sitter/tree-sitter#5625

Signed-off-by: Georges Savoundararadj <savoundg@amazon.com>
2026-05-30 21:42:31 -04:00
Will Lillis 17125cefa6 fix(lib): update doc comment for ts_parser_parse 2026-05-30 04:11:53 -04:00
dependabot[bot] e39ef3e700 build(deps): bump serde_json from 1.0.149 to 1.0.150 in the cargo group
Bumps the cargo group with 1 update: [serde_json](https://github.com/serde-rs/json).


Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-26 09:59:47 +02:00
Will Lillis 30c00bc4fe fix(cli): place generate artifacts next to grammar file rather than cwd
- If grammar.js is passed, place it in the parent directory
- If grammar.json  is passed, place it in the grandparent directory
2026-05-24 22:12:56 -04:00
Will Lillis a5c0d24749 fix(test): restore corpus test header blank line and divider tie behavior 2026-05-23 12:32:46 -04:00
dependabot[bot] b1fa9725cf build(deps): bump brace-expansion in /crates/cli/eslint
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 5.0.5 to 5.0.6.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.5...v5.0.6)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 5.0.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-19 10:42:31 +02:00
dependabot[bot] edae7391ed ci: bump korthout/backport-action in the actions group
Bumps the actions group with 1 update: [korthout/backport-action](https://github.com/korthout/backport-action).


Updates `korthout/backport-action` from 4.5.1 to 4.5.2
- [Release notes](https://github.com/korthout/backport-action/releases)
- [Commits](https://github.com/korthout/backport-action/compare/v4.5.1...v4.5.2)

---
updated-dependencies:
- dependency-name: korthout/backport-action
  dependency-version: 4.5.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-19 09:51:24 +02:00
Max Brunsfeld a53c3b03a0
fix(wasm): load supertype tables for ABI 15 grammars (#5605)
The wasm store gated supertype_symbols / supertype_map_slices /
supertype_map_entries copies on abi_version > LANGUAGE_VERSION_WITH_RESERVED_WORDS,
but every other consumer (language.c, query.c) treats those tables as
present when abi_version >= LANGUAGE_VERSION_WITH_RESERVED_WORDS.

A Wasm grammar built at ABI exactly 15 with supertype_count > 0 ends up
with supertype_count copied into the native TSLanguage but supertype_map_slices
left NULL. ts_query__analyze_patterns then calls ts_language_subtypes,
which dereferences self->supertype_map_slices[supertype] and crashes.
2026-05-18 16:33:55 -04:00
Will Lillis f535c3bb97 fix(test): write fixture headers once
Several test functions compile the same grammar fixture.
Tests sharing a grammar name also share a src_dir. Each test also
unconditionally writes the three tree-sitter headers into
src_dir/tree_sitter/, leading to a race.

Write the three headers exactly once per src_dir, controlled via a
global `HashSet`.
2026-05-17 19:55:37 -04:00
Will Lillis 71040925fe perf(cli): stream CST rendering via Display wrappers
Co-authored-by: Amaan Qureshi <git@amaanq.com>
2026-05-17 15:02:08 -04:00
Will Lillis ffea4e8e58 fix(cli): rework coloring abstraction and fix NO_COLOR handling
Co-authored-by: Amaan Qureshi <git@amaanq.com>
2026-05-17 15:02:08 -04:00
Christian Clason d4e7f9793c build(deps): bump wasi-sdk to v33 2026-05-16 11:45:48 +02:00
Christian Clason 3a260021dd build(deps): bump wasmtime-c-api to v36.0.9 2026-05-16 11:45:39 +02:00
dependabot[bot] a858378ce7 build(deps): bump cc from 1.2.61 to 1.2.62 in the cargo group
Bumps the cargo group with 1 update: [cc](https://github.com/rust-lang/cc-rs).


Updates `cc` from 1.2.61 to 1.2.62
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.61...cc-v1.2.62)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.62
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-12 09:13:21 +02:00
dependabot[bot] aa83bf0402 ci: bump korthout/backport-action in the actions group
Bumps the actions group with 1 update: [korthout/backport-action](https://github.com/korthout/backport-action).


Updates `korthout/backport-action` from 4.5.0 to 4.5.1
- [Release notes](https://github.com/korthout/backport-action/releases)
- [Commits](https://github.com/korthout/backport-action/compare/v4.5.0...v4.5.1)

---
updated-dependencies:
- dependency-name: korthout/backport-action
  dependency-version: 4.5.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-12 09:13:01 +02:00
Will Lillis 50bb81484d fix(generate): consider reserved words when removing unused rules 2026-05-10 23:53:48 -04:00
Will Lillis 3b328005c9 fix(generate): rewrite parse_grammar with forward DFS
The previous implementation of `InputGrammar::normalize` (inlined  in
`parse_grammar` iterated over every variable, checking whether  it was
reachable _backwards_ from teh root by recursing over rules that
referenced it.

This means that each and every top level call re-traversed the entire
graph. The runtime performance of this backwards walk was dependent on
the _order_ of rules as declared in `grammar.js`. All existing grammars
have an ordering that's reasonably friendly to this iteration pattern
(BFS-ish order, top down from the start rule), but this leaves us open
to a catastrophic performance cliff.

Instead, seed a `used` set with the start rule, the word token, and an
names referenced from `extras`/`externals`. Then propagate via direct rule
references. This yields anywhere from a 2-~2200x speedup for
`parse_grammar`. This greatly speeds up `--no-parser` runs, but is
relatively unimportant for `parser.c` generation for _existing_
grammars. The important piece is eliminating the potential cliff.
2026-05-10 23:53:48 -04:00
Will Lillis a376ad491f fix(generate): improve error message for nonterminals used in immediate token rule 2026-05-10 10:39:48 -04:00
𝙽!𝙻 21cfae7b56
Validate Wasm language memory reads (#5569) 2026-05-06 11:54:31 -07:00
Will Lillis 5cac4316db fix(cli): account for process versions > 5 in the parse command's pretty
debug output.

The initial implementation of `--debug pretty` assumed process version
was bounded by `MAX_VERSION_COUNT`. However, we also have to account for
`MAX_VERSION_COUNT_OVERFLOW` as well as `halted_version_count`.
2026-05-05 18:17:07 -04:00
dependabot[bot] bc2802abed build(deps): bump clap_complete from 4.6.2 to 4.6.3 in the cargo group
Bumps the cargo group with 1 update: [clap_complete](https://github.com/clap-rs/clap).


Updates `clap_complete` from 4.6.2 to 4.6.3
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.2...clap_complete-v4.6.3)

---
updated-dependencies:
- dependency-name: clap_complete
  dependency-version: 4.6.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-05 08:19:48 +02:00
dependabot[bot] 16e48018b3 ci: bump korthout/backport-action in the actions group
Bumps the actions group with 1 update: [korthout/backport-action](https://github.com/korthout/backport-action).


Updates `korthout/backport-action` from 4.4.0 to 4.5.0
- [Release notes](https://github.com/korthout/backport-action/releases)
- [Commits](https://github.com/korthout/backport-action/compare/v4.4.0...v4.5.0)

---
updated-dependencies:
- dependency-name: korthout/backport-action
  dependency-version: 4.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-05 08:19:21 +02:00
Christian Clason 85f985a778 build(deps): bump wasmtime-c-api to v36.0.8 2026-05-05 08:19:02 +02:00
Will Lillis 8d737aa238 docs: note zero point unbounded behavior in query functions 2026-04-30 04:28:59 -04:00
dependabot[bot] aedab6e0f2 build(deps): bump postcss from 8.5.8 to 8.5.12 in /lib/binding_web
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.8 to 8.5.12.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.8...8.5.12)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-29 11:12:37 +02:00
dependabot[bot] fb3e029911 ci: bump the actions group with 2 updates
Bumps the actions group with 2 updates: [korthout/backport-action](https://github.com/korthout/backport-action) and [actions/setup-node](https://github.com/actions/setup-node).


Updates `korthout/backport-action` from 4 to 4.4.0
- [Release notes](https://github.com/korthout/backport-action/releases)
- [Commits](https://github.com/korthout/backport-action/compare/v4...v4.4.0)

Updates `actions/setup-node` from 6.3.0 to 6.4.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6.3.0...v6.4.0)

---
updated-dependencies:
- dependency-name: korthout/backport-action
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: actions/setup-node
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-28 09:30:25 +02:00
dependabot[bot] 6c674cb5bd build(deps): bump cc from 1.2.60 to 1.2.61 in the cargo group
Bumps the cargo group with 1 update: [cc](https://github.com/rust-lang/cc-rs).


Updates `cc` from 1.2.60 to 1.2.61
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.60...cc-v1.2.61)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.61
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-28 09:29:50 +02:00
Wilco Kruijer fb348c3493 wasm: add Language.loadSync for synchronous loading
Co-authored-by: Amaan Qureshi <git@amaanq.com>
2026-04-26 18:58:50 -04:00
Amaan Qureshi 43dc8eadbe query: fix finished state heap invariants 2026-04-26 18:03:55 -04:00
Will Lillis 123fb1c13c perf(query): use min-heap for finished_states in next_capture
`ts_query_cursor_next_capture` linearly scanned all finished states to
find the one with the earliest next capture byte offset. With deeply
nested code, this O(n) scan per capture caused the highlight crate to
hang for minutes on large files.

Replace the linear scan with a min-heap over the finished_states array,
keyed by (next_capture_byte_offset, pattern_index, id). The heap is
maintained lazily: ts_query_cursor__advance uses plain array_push
(preserving FIFO insertion order), and next_capture sifts new elements
into the heap on entry via a tracked heap_size boundary. This preserves
the documented "order found" guarantee for next_match while giving
next_capture O(log n) per call.
2026-04-26 18:03:55 -04:00
Will Lillis 361f293a73 fix(query): widen capture list pool from uint16_t to uint32_t
Commit 1f6eac55 ("query: Use uint32_t for capture list IDs") widened
QueryState.capture_list_id to uint32_t and removed the 65536 pool cap,
but left the pool function signatures as uint16_t. This caused silent
truncation when the pool exceeded 65535 entries, leading to a segfault.
2026-04-26 18:03:55 -04:00
Amaan Qureshi e24c109c82 ci: run on illumos 2026-04-26 13:23:32 -04:00
Petr Sumbera 77cb23fccc lib: add Solaris support to the portable endian header
Solaris does not provide <endian.h> or <sys/endian.h>, but it does expose
byte-order definitions and conversion helpers via <sys/isa_defs.h> and
<sys/byteorder.h>.

Add a __sun branch so the portable header defines __BYTE_ORDER and the
htobe*/le*toh conversions on Solaris.

Co-authored-by: Amaan Qureshi <git@amaanq.com>
2026-04-26 13:23:32 -04:00
Daniel Jalkut 0f6780b9a3 lexer: pass code-unit length to U16_NEXT in UTF-16 decoders
`ts_decode_utf16_le` and `ts_decode_utf16_be` passed a byte length to
`U16_NEXT_LE` and `U16_NEXT_BE`, but those macros count `uint16_t` code
units. If a lead surrogate was the last code unit in a chunk, the decoder
could peek past the chunk and combine it with adjacent memory.

This commit passes the code-unit length to the UTF-16 macros, and fails
with `TS_DECODE_ERROR` when a chunk is too short to contain even one full
code unit. This lets the lexer retry with a fresh chunk or advance through
the invalid byte as it already does.

Co-authored-by: Will Lillis <will.lillis24@gmail.com>
Co-authored-by: Amaan Qureshi <git@amaanq.com>
2026-04-26 03:38:47 -04:00
Amaan Qureshi 475c48d1e3 docs: add AI policy
Co-authored-by: Christian Clason <c.clason@uni-graz.at>
2026-04-26 02:58:56 -04:00
Will Lillis 457eb295b7 fix(generate): pass default optimization level in
`generate_parser_for_grammar`

Passing `empty` here causes some grammars (i.e. tree-sitter-cpp) to fail
to generate.
2026-04-24 08:52:18 +02:00
Antonin Delpeuch eccef997df
feat(dist): enable install via cargo binstall (#5533) 2026-04-24 08:52:01 +02:00
Will Lillis 15154504de fix(ci): include fixture lockfile in cache hash 2026-04-23 03:31:00 -04:00
Will Lillis d6e33d0722 test: bump c test fixture to v0.24.2 2026-04-23 03:31:00 -04:00
Volker Mische 4cb11acd46
fix(loader): allow filenames with dots (#5529) 2026-04-22 03:41:04 -04:00
dependabot[bot] 4701df97c0 build(deps): bump the cargo group with 3 updates
Bumps the cargo group with 3 updates: [clap_complete](https://github.com/clap-rs/clap), [webbrowser](https://github.com/amodm/webbrowser-rs) and [bitflags](https://github.com/bitflags/bitflags).


Updates `clap_complete` from 4.6.1 to 4.6.2
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.1...clap_complete-v4.6.2)

Updates `webbrowser` from 1.2.0 to 1.2.1
- [Release notes](https://github.com/amodm/webbrowser-rs/releases)
- [Changelog](https://github.com/amodm/webbrowser-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amodm/webbrowser-rs/compare/v1.2.0...v1.2.1)

Updates `bitflags` from 2.11.0 to 2.11.1
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/2.11.0...2.11.1)

---
updated-dependencies:
- dependency-name: clap_complete
  dependency-version: 4.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: webbrowser
  dependency-version: 1.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: bitflags
  dependency-version: 2.11.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-21 09:38:08 +02:00
dependabot[bot] 5c1dd38c45 ci: bump actions/setup-node from 6 to 6.3.0 in the actions group
Bumps the actions group with 1 update: [actions/setup-node](https://github.com/actions/setup-node).


Updates `actions/setup-node` from 6 to 6.3.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6...v6.3.0)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-21 09:37:49 +02:00
Chinmay Dalal 20bdf3a8a0
feat(bindings): update zig template to 0.16 2026-04-20 09:31:18 +02:00
changw98ic aff9b9d92e fix: update broken link to Parser.Language in template
The link `https://tree-sitter.github.io/node-tree-sitter/interfaces/Parser.Language.html` returns a 404 error.

In the current typedoc output, the Language interface is at `/interfaces/Language.html` (without the `Parser.` prefix), as the documentation flattens namespace members.

Fixes #5525
2026-04-17 08:20:05 +02:00
Chinmay Dalal 24a64db7cc build(zig): update to zig 0.16 2026-04-15 14:54:53 +02:00
dependabot[bot] b3a752aa5b ci: bump the actions group with 2 updates
Bumps the actions group with 2 updates: [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) and [actions/github-script](https://github.com/actions/github-script).


Updates `actions/upload-pages-artifact` from 4 to 5
- [Release notes](https://github.com/actions/upload-pages-artifact/releases)
- [Commits](https://github.com/actions/upload-pages-artifact/compare/v4...v5)

Updates `actions/github-script` from 8 to 9
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v8...v9)

---
updated-dependencies:
- dependency-name: actions/upload-pages-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/github-script
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 08:19:47 +02:00
dependabot[bot] 63b7d3b811 build(deps): bump rand from 0.10.0 to 0.10.1
Bumps [rand](https://github.com/rust-random/rand) from 0.10.0 to 0.10.1.
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.0...0.10.1)

---
updated-dependencies:
- dependency-name: rand
  dependency-version: 0.10.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 08:18:47 +02:00
Christian Clason 75fe13e7aa build(deps): cargo update 2026-04-11 18:29:00 -04:00
Christian Clason 8178cfddd8 build(deps): bump wasmtime-c-api to v36.0.7 2026-04-10 00:41:06 +02:00
Christian Clason 78714eb1b8 ci(nvim-ts): further improvements to Neovim build
* Build Neovim on Windows as well
* Use latest stable release instead of `HEAD` as base
* Run parser check (prints ABI)
2026-04-07 22:55:22 +02:00
dependabot[bot] b018c20018 build(deps): bump vite from 7.3.1 to 7.3.2 in /lib/binding_web
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.3.1 to 7.3.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.3.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-07 10:10:18 +02:00
Christian Clason aa2c18895b ci(nvim-ts): build nvim against PR lib
Problem: The nvim-treesitter tests use a prebuilt Neovim, which means
changes to the library are not tested and parser generation is locked at
the ABI Neovim supports.

Solution: Build Neovim against the PR version of the library on Ubuntu
and macOS. On Windows, we still download binaries (because one does not
simply build Neovim on Windows...) so we still can check backward
compatibility.
2026-04-05 13:27:17 +02:00
Will Lillis ec0f2b59ee perf(generate): use bitsets for coincident tokens and TokenConflictMap
- replace `Vec<Vec<ParseStateId>>` with a flat bitset in
  `CoincidentTokenIndex` for membership queries

The previous representation stored full lists of parse state IDs for
each token pair. Since only membership (yes/no) is needed for most
queries, a flat bitset (`Vec<u64>` indexed as `a * n + b`) is
better. Both `(a,b)` and `(b,a)` bits are set during construction
so `contains()` needs no min/max normalization. The original
`entries: Vec<Vec<ParseStateId>>` and `states_with()` method are
retained alongside the bitset, because `identify_keywords` needs
to iterate over the specific parse states where two tokens co-occur
rather than scanning all states. ~8% walltime reduction for
tree-sitter-bash.

- add word-aligned per-row bitsets to `CoincidentTokenIndex` for
  vectorized intersection checks

Row `a` spans `[a * row_words .. (a+1) * row_words]` where
`row_words = n.div_ceil(64)`. This enables callers in
`build_lex_table` to perform word-level AND operations against
`TokenSet::terminal_bits_words()` instead of iterating individual
token indices. ~6% walltime reduction on tree-sitter-bash.

- add `TokenSet::terminal_bits_words()` accessor and `#[inline]` on
  `BitVec::insert_all`

Expose the raw `&[u64]` backing the terminal bitset so callers can
perform word-level bitwise intersection. Mark `insert_all` as
`#[inline]` to allow the compiler to optimize the hot OR loop.
2026-04-04 17:58:29 -04:00
Will Lillis 13a7a04e79 perf(generate): optimize NFA operations and token conflict precomputation
- precompute a flat NFA-state-to-variable-index lookup table

Build a `Vec<usize>` mapping each NFA state ID to its owning
variable index up front in `TokenConflictMap::new`, replacing
repeated binary searches in `compute_conflict_status` with
array lookups. Also simplifies `mark_fragile_tokens` to
collect terminal indices into a `Vec` rather than maintaining
a boolean mask.

- replace `FxHashSet<Vec<u32>>` visited set with `FxHashSet<u64>`
  using a hash-of-states key

In `compute_conflict_status`, the visited-state-set previously
stored cloned `Vec<u32>` NFA state sets. Replace this with an
`FxHashSet<u64>` keyed by hashing the sorted state slice via
`FxHasher`. This eliminates the `Vec` clone on every BFS step.

- reduce allocations in `NfaCursor::group_transitions`

Reuse a single `CharacterSet` buffer across iterations via `assign`
and `mem::take` instead of cloning the input on every raw
transition. Also, replace `Vec::insert` with `Vec::push` when
splitting intersection transitions. The final sort makes
mid-loop ordering irrelevant, and the disjointness of the split
sets guarantees correctness.

- pre-allocate `NfaTransition` result vectors with a capacity of 8
  and use `swap_remove` instead of `remove` when merging duplicate
  transition entries

- lazily compute `within_separator` in `compute_conflict_status`

Most BFS states have no completions, so the separator check
(which iterates all cursor transitions) is never needed in those
iterations. Wrap it in `Option::get_or_insert_with` to defer
computation until first use.

- add `NfaCursor::transitions_and_any_sep` to fuse the transitions
  and separator-check passes

Callers like `build_lex_table::add_state` previously called
`transitions()` and then `transition_chars().any(|sep|)` separately,
iterating the raw NFA transitions twice. The fused method computes
both in a single pass.
2026-04-04 17:58:29 -04:00
Will Lillis f1142b6e42 perf(generate): use FxHasher over default hash implementation
Replace `std::collections::{HashMap, HashSet}` with
`rustc_hash::{FxHashMap, FxHashSet}` throughout `generate`. `FxHash`
is _much_ faster than the default SipHash for small integer keys,
which are used extensively through `generate`. The one "risk" here is
that `FxHash` is more vulnerable to DDOS attacks, but that isn't as much
of a concern for us here.
2026-04-04 17:58:29 -04:00
Will Lillis 6c8ac80be9 perf(generate): optimize token conflict analysis and symbol handling
- use bitflags for `TokenConflictStatus`

Replace the individual boolean fields on the conflict status type
with a `bitflags!` representation. This takes up significantly less
space and allows for faster combination/testing in `token_conflicts`.

- hoist loop bounds in the merge-join loops of `states_conflict`

Extract `Vec::len()` outside of `while` loops to avoid recomputing
bounds on every iteration. Remove the now-unused `row_offset` /
`does_conflict_at` indirection on `TokenConflictMap`, inlining the
conflict check directly.

- optimize `variable_index_for_nfa_state` in `grammars.rs`

Small optimization to the NFA state-to-variable index mapping.

- refactor symbol key into a proper newtype

Extract the raw `u64` symbol key bit manipulation into a `key_index`
helper function for some extra type safety.
2026-04-04 17:58:29 -04:00
Will Lillis d4db0a32f0 perf(generate): optimize minimize_parse_table with precomputation and compact types
- use a boolean `Vec` instead of `Vec::contains` in `dedup`

Replace linear containment checks (`split_state_ids.contains()`)
with a constant lookup indexed by state ID.

- narrow parse state IDs from `usize` to `u16`

Reduces memory footprint and improves packing in `ParseTable` and
`build_parse_table`. This is more cache-friendly and reduces peak rss by
~1-2%.

- precompute lookup tables for parse table minimization

Build several maps up front (symbol-to-action, nonterminal indices,
symbol keys) rather than recomputing them in the hot minimization
loop. The `SymbolKey` optimization is the largest single win: it
packs each `Symbol`'s type tag and index into a single u64, enabling
single-instruction comparisons in the merge-join that dominates
`states_conflict`. This gives a significant speedup for larger
grammars.

- store ParseTableEntry` refs in the precomputed entry maps to
  avoid `IndexMap::get_index` lookups in the inner loop of
  `states_conflict`

This eliminates an indirect lookup per comparison in the merge-join.

- use unchecked array accesses in hot loops

Where indices are known valid from construction (e.g. the merge-join
in `states_conflict` and dedup), skip bounds checks in the innermost
loops. The compiler can _usually_ prove this and remove the checks
itself, but not always.
2026-04-04 17:58:29 -04:00
Will Lillis 86d99e4f8e fix(ci): include wasi-sdk and binaryen version files in fixture cache
hash
2026-04-04 17:03:58 -04:00
Christian Clason 83d61ea20e build(deps): bump binaryen to v129 2026-04-04 21:32:49 +02:00
Christian Clason 35d4954030 feat(generate): don't require tree-sitter.json for ABI 15
Problem: Many parser projects (about 100 out of ~330 tracked by
nvim-treesitter) still do not contain a `tree-sitter.json` config file,
so `tree-sitter generate` refuses to generate ABI 15 parsers due to the
lack of version information. This prevents these parsers from profiting
from other improvements in ABI 15+ and will eventually make them
unusable when ABI 14 support is dropped.

Solution: Simply use a default version `0.0.0` if no `tree-sitter.json`
is found.
2026-04-04 11:14:43 +02:00
Christian Clason bf37664420 ci(nvim-ts): minor improvements
* remove obsolete msvc-dev-cmd action (runners come with build tools)
* build tree-sitter CLI with --profile optimize (longer build time due
  to LTO, but generate workflow should profit)
* increase generate parallelism to 3
2026-04-04 04:38:21 -04:00
dependabot[bot] 422d7cc503 build(deps): bump the npm group across 1 directory with 4 updates
Bumps the npm group with 4 updates in the /lib/binding_web directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [dts-buddy](https://github.com/sveltejs/dts-buddy), [esbuild](https://github.com/evanw/esbuild) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).

Updates `@types/node` from 24.10.1 to 25.5.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `dts-buddy` from 0.6.2 to 0.7.0
- [Release notes](https://github.com/sveltejs/dts-buddy/releases)
- [Changelog](https://github.com/sveltejs/dts-buddy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/dts-buddy/compare/v0.6.2...v0.7.0)

Updates `esbuild` from 0.27.1 to 0.27.4
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/v0.27.4/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.1...v0.27.4)

Updates `typescript-eslint` from 8.48.1 to 8.58.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.58.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.5.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm
- dependency-name: dts-buddy
  dependency-version: 0.7.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: esbuild
  dependency-version: 0.27.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: typescript-eslint
  dependency-version: 8.58.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-04 01:21:32 -04:00
Christian Clason 3a4245ba23 build(deps): cargo update 2026-04-04 01:20:58 -04:00
dependabot[bot] 7c1e6861fb build(deps): bump the npm group across 1 directory with 4 updates
Bumps the npm group with 4 updates in the /lib/binding_web directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [dts-buddy](https://github.com/sveltejs/dts-buddy), [esbuild](https://github.com/evanw/esbuild) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `@types/node` from 24.10.1 to 25.5.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `dts-buddy` from 0.6.2 to 0.7.0
- [Release notes](https://github.com/sveltejs/dts-buddy/releases)
- [Changelog](https://github.com/sveltejs/dts-buddy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/dts-buddy/compare/v0.6.2...v0.7.0)

Updates `esbuild` from 0.27.1 to 0.27.4
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/v0.27.4/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.1...v0.27.4)

Updates `typescript-eslint` from 8.48.1 to 8.58.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.58.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.5.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm
- dependency-name: dts-buddy
  dependency-version: 0.7.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: esbuild
  dependency-version: 0.27.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: typescript-eslint
  dependency-version: 8.58.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-03 17:46:45 +02:00
Christian Clason 64698af1ac ci(actions): bump actions/cache to v5
Apparently dependabot doesn't cover the actions/cache/action.yml.
2026-04-01 01:35:49 -04:00
Will Lillis ba17ca14a0 fix(loader): remove CROSS_RUNNER temorary directory path
This isn't used in CI anymore.
2026-03-31 04:00:43 -04:00
Will Lillis 8eafafaa16 fix(test): split up cli testing test
Now that the loader race condition is resolved, these tests no longer
need to be grouped together.
2026-03-31 04:00:43 -04:00
Will Lillis f303a957a3 fix(loader): replace flock-based compilation lock with atomic lock file
The previous two-phase locking scheme (probe existing lock file, then
create and compile) had TOCTOU races between phases that caused
spurious failures in CI when tests compiled grammars concurrently.

Replace with a single-phase approach using `create_new` as the sole
synchronization primitive. An RAII `LockFile` guard ensures cleanup
on drop (including panics). Only "builders" attempt to acquire the
lock. Loaders simply load the file. Builders compile a temporary path
and then rename, so loaders are guaranteed a valid shared library.
The winning builder compiles and then drops the lock. Losers poll for
lock file removal, then load. Stale locks from killed processes are
detected via a timeout and an appropriate error message is displayed
to the user.
2026-03-31 04:00:43 -04:00
Will Lillis 3294a64027 perf(cli): replace regex with manual parsing in test file parser
This started as a simple one to one rewrite, just removing the regexes,
and quickly devolved into a rewrite of the test parsing logic. In
addition to the memory enhancements, the general flow should be much
clearer now. A few data points:

- JS:   walltime -1.3%, peak rss -10.2%
- C:    walltime -5.7%, peak rss -4.5%
- Rust: walltime -3.8%, peak rss -2.2%
2026-03-31 04:00:43 -04:00
Franklin Chen 7c2e757c03 generate: avoid panicking when a supertype only has hidden external token children
This commit skips adding entries to the subtype map when the subtypes
list is empty to avoid a lookup failure in the topological sort during
node type generation.

Co-authored-by: Amaan Qureshi <git@amaanq.com>
2026-03-31 02:12:56 -04:00
futsuuu 0d4f5cb13f fix(loader): don't discard CC wrapper command specified in CC variable 2026-03-31 01:43:43 -04:00
Max Brunsfeld d3ff0ce81d
Fix wasm loading of languages w/ multiple reserved word sets (#5475) 2026-03-30 22:27:57 -07:00
Will Lillis 791d7cead4 perf(cli): minor allocation and write call reductions
- Cache tree byte range length to avoid redundant FFI calls per test
- Combine 5 separate XML attribute write! calls into one
- Pre-allocate format_sexp output buffer to avoid repeated growth
2026-03-31 01:27:05 -04:00
Will Lillis 827bcdabd9 perf(cli): buffer stdout in parse and query output
Wrap stdout in a 64KB BufWriter when writing `parse` output. The tree
walking loop makes many small write calls (parentheses, indentation,
node kinds, ranges, etc.) which are expensive without buffering.

When parsing the jquery.js corpus file, cuts the total time roughly in
half. These savings only show when piping the result to a file,
otherwise terminal rendering time usually dominates, hiding all gains.

Also do the same for the `query` command's output.
2026-03-31 01:27:05 -04:00
Will Lillis 1ec3b3aabe generate: remove comment cleaning for grammar.json
A quick search through github shows that nobody utilizes this, so it's
effectively dead code.
2026-03-31 01:23:15 -04:00
Will Lillis b53ddd3d54 perf(loader): extract name from grammar.json without regex
We have to pay the cost of compiling the regex at runtime, and the
`LazyLock` overhead for each access. The pattern is simple enough that
we can manually parse and extract.

`grammar_json_name` is now ~116x faster on cold start, ~5.6x faster warm
(after the regex has been compiled). Both are fast enough to not matter
much in practice, but some perf gains and eliminating global state is a
win.
2026-03-31 01:23:15 -04:00
dependabot[bot] 1fcbacfff9 ci: bump the actions group with 2 updates
Bumps the actions group with 2 updates: [actions/configure-pages](https://github.com/actions/configure-pages) and [actions/deploy-pages](https://github.com/actions/deploy-pages).


Updates `actions/configure-pages` from 5 to 6
- [Release notes](https://github.com/actions/configure-pages/releases)
- [Commits](https://github.com/actions/configure-pages/compare/v5...v6)

Updates `actions/deploy-pages` from 4 to 5
- [Release notes](https://github.com/actions/deploy-pages/releases)
- [Commits](https://github.com/actions/deploy-pages/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/configure-pages
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/deploy-pages
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-31 00:34:05 +02:00
dependabot[bot] e65b7f2875 build(deps): bump cc from 1.2.57 to 1.2.58 in the cargo group
Bumps the cargo group with 1 update: [cc](https://github.com/rust-lang/cc-rs).


Updates `cc` from 1.2.57 to 1.2.58
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.57...cc-v1.2.58)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.58
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-31 00:24:03 +02:00
Will Lillis 01dc3dba18 fix(parser): sort error recovery reductions for deterministic ordering
The `LookaheadIterator` visits symbols in group order for small parse
states, which can produce `reduce_actions` in a different order than the
original linear symbol scan (pre c1379718). Since reductions are applied
sequentially and the last reduction version survives, different orderings
lead to different error recovery outcomes (e.g. losing nodes from ERROR
trees).

Sort the reduce_actions array by symbol (descending) after collection.
The array is typically 1-5 entries, so the insertion sort cost is
negligible and the full optimization speedup is preserved.
2026-03-29 21:36:29 -04:00
Will Lillis 0535b0ca37 fix(lib): correct various typos 2026-03-28 05:23:10 -04:00
Will Lillis 9a48405a87 fix(docs): correct various typos 2026-03-28 05:23:10 -04:00
Will Lillis cf8719f132 fix(rust): correct various typos 2026-03-28 05:23:10 -04:00
Will Lillis 1a82df3cac fix(rust): address new nightly lints 2026-03-28 05:23:10 -04:00
Will Lillis c1379718ab perf(parser): use LookaheadIterator in error recovery's all-symbol scan
In `ts_parser__do_all_potential_reductions`, when `lookahead_symbol` is
0 (error recovery), the code scanned every symbol from 1 to
`token_count` calling ts_language_table_entry for each with most returning
empty. Replace with `LookaheadIterator` which efficiently visits only symbols
with valid actions, yielding 7-20x fewer lookups for typical grammars.

Error recovery throughput improves ~29% (JS) / ~39% (C). Valid-code
parsing is unaffected.
2026-03-28 05:21:50 -04:00
Aaron Paterson cb01ffcc3f docs: update WASM build requirements for wasi-sdk
Since v0.26.1, `tree-sitter build --wasm` uses wasi-sdk instead of
Emscripten. The CLI automatically downloads wasi-sdk on first use,
so Emscripten, Docker, and Podman are no longer required.

Update the web binding README to reflect this change.

See #4393 for the original switch to wasi-sdk.
2026-03-27 15:07:54 +01:00
dependabot[bot] cc2ed5e85f build(deps): bump brace-expansion in /crates/cli/eslint
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 5.0.2 to 5.0.5.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.2...v5.0.5)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 5.0.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-27 14:57:11 +01:00
dependabot[bot] e957e8ce0d build(deps): bump flatted from 3.4.1 to 3.4.2 in /crates/cli/eslint
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.4.1 to 3.4.2.
- [Commits](https://github.com/WebReflection/flatted/compare/v3.4.1...v3.4.2)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-27 14:55:25 +01:00
dependabot[bot] 6821fa7611 build(deps): bump flatted from 3.3.2 to 3.4.2 in /lib/binding_web
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.2 to 3.4.2.
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.2...v3.4.2)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-27 14:55:06 +01:00
dependabot[bot] fd77bda97a build(deps): bump cc from 1.2.56 to 1.2.57 in the cargo group
Bumps the cargo group with 1 update: [cc](https://github.com/rust-lang/cc-rs).


Updates `cc` from 1.2.56 to 1.2.57
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.56...cc-v1.2.57)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.57
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-23 23:02:18 +01:00
dependabot[bot] 8769254bda ci: bump actions/create-github-app-token in the actions group
Bumps the actions group with 1 update: [actions/create-github-app-token](https://github.com/actions/create-github-app-token).


Updates `actions/create-github-app-token` from 2 to 3
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](https://github.com/actions/create-github-app-token/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-23 23:01:49 +01:00
Will Lillis 08d676b417 fix(lib): document invariants that must be upheld for TSInputEdit 2026-03-22 23:17:01 -04:00
Will Lillis df3fa372a6 fix(generate): allow disabling qjs-rt feature from CLI
The workspace dependency for `tree-sitter-generate` did not set
`default-features = false`, so Cargo always enabled its default
features (including `qjs-rt` and thus `rquickjs`) regardless of
the CLI's `--no-default-features` flag.

Additionally, `tree-sitter-generate` failed to compile without the
`load` feature due to unconditional references to `cfg`-gated items.

- Set `default-features = false` on the workspace `tree-sitter-generate`
  dependency so the CLI's feature forwarding actually takes effect.
- Explicitly enable the `load` feature in the CLI's dependency on
  `tree-sitter-generate`, since the CLI needs `load`-gated functions
  unconditionally.
- Gate necessary imports behind `#[cfg(feature = "load")]` to fix
  `tree-sitter-generate`'s build without the `load` feature.
2026-03-21 05:51:10 -04:00
Will Lillis 6011985a6c fix(loader): unconditionally import Mutex
Also, remove  the `LazyLock` for `WASM_TOOL_LOCK`. This isn't necessary
as `Mutex::new` is valid in const contexts
2026-03-17 02:27:50 -04:00
dependabot[bot] af67543764 build(deps): bump anstyle from 1.0.13 to 1.0.14 in the cargo group
Bumps the cargo group with 1 update: [anstyle](https://github.com/rust-cli/anstyle).


Updates `anstyle` from 1.0.13 to 1.0.14
- [Commits](https://github.com/rust-cli/anstyle/compare/v1.0.13...v1.0.14)

---
updated-dependencies:
- dependency-name: anstyle
  dependency-version: 1.0.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-16 22:56:08 +01:00
dependabot[bot] 94f48e3ebf build(deps): bump flatted from 3.3.1 to 3.4.1 in /crates/cli/eslint
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.1 to 3.4.1.
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.1...v3.4.1)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-14 19:19:06 +01:00
Christian Clason 0dd08ce747 ci(release): publish zip archives for tree-sitter-cli
Problem: Publishing the built tree-sitter-cli artifacts as gzipped
binaries prevents installing them with `cargo binstall` or packaging
them for Windows Package Manager (`winget`).

Solution: Also create a zip archive for each binary, named
`tree-sitter-cli` to match now-common downstream usage. (Most package
managers have separate `tree-sitter-cli` and `tree-sitter` (library)
packages.)
2026-03-13 15:52:21 -04:00
Will Lillis 30d9c675a4 docs: indicate that dashes are not permitted in parser names 2026-03-13 15:35:14 -04:00
Will Lillis 30ed8da439 Revert "feat: allow - in grammar names"
This reverts commit 7d3c321253.
2026-03-13 15:35:14 -04:00
Christian Clason 5f7326afd5 build(deps): bump binaryen to v127 2026-03-13 15:31:37 -04:00
Christian Clason 2c7375bdb2 build(deps): bump wasi-sdk to v32 2026-03-13 15:31:37 -04:00
Christian Clason a8845c066c build(deps): cargo update 2026-03-13 15:28:46 -04:00
Christian Clason 8d656ed837 ci(tests): run required tests on all PRs
Problem: `build` tests are marked as "required" but skipped when only
touching documentation, leading to such PRs not being mergable.

Solution: Run tests on all PRs and all commits to the `master` or
`release-0.x` branches.
2026-03-13 15:21:47 -04:00
Riley Bruins cf302b07d1 fix(query): don't add copies for quantifier steps outside alternations 2026-03-04 10:32:50 +01:00
Riley Bruins a95fff5477 refactor(query): remove alternative_is_immediate
This is implied by `is_pass_through`.
2026-03-04 02:44:53 -05:00
dependabot[bot] 495b069667 ci: bump the actions group with 3 updates
Bumps the actions group with 3 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact), [actions/download-artifact](https://github.com/actions/download-artifact) and [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance).


Updates `actions/upload-artifact` from 6 to 7
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

Updates `actions/download-artifact` from 7 to 8
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v7...v8)

Updates `actions/attest-build-provenance` from 3 to 4
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/attest-build-provenance
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-03 00:17:34 +01:00
Will Lillis b27de1038f fix(rust): remove unused dependencies from loader and lib 2026-03-02 09:14:28 +01:00
Laurent Cheylus 2f747dc9b1 fix(loader): link with libc on OpenBSD to compile parser
Fix tree-sitter/tree-sitter#5333

Signed-off-by: Laurent Cheylus <foxy@free.fr>
2026-03-01 17:07:10 -05:00
John Eismeier 1f9b274dfb
chore: fix typos in messages and documentation
Signed-off-by: John E <jeis4wpi@outlook.com>
2026-03-01 11:40:15 +01:00
Riley Bruins 4ae90615d1 chore(parser): return NULL, not false, for incomplete parse
Small nit; `NULL` is returned everywhere else in this function for an
incomplete parse.
2026-03-01 03:41:27 -05:00
Will Lillis 5fa7514fee perf(generate): several small optimizations
* reduce inner loop range in `CoincidentTokenIndex::new`

The indices computed from `a, b` and `b, a` are identical, so there's
no reason to iterate over the entire set of terminal indices in the
inner loop.

* remove redundant check for `does_match_same_string` in
  `token_conflicts`

`does_match_same_string` is already covered within `does_conflict`, so
OR-ing the result of the two together is wasteful.

* mark several functions available for `inline`

Reduces walltime by 0-3% depending on the grammar.

* don't continually reserve space in render buffer

Reserving space up front for containers is great. Doing this in a loop
can lead to _more_ allocations, hurting performance.

* pre-collect terminal indices in `CoincidentTokenIndex`

In `CoincidentTokenIndex::new`, the inner loop re-iterates the
`IndexMap` keys and re-checks is_terminal() for every outer iteration.
This information can be collected once per state, avoiding redundant
calculations.
2026-02-28 18:41:29 +01:00
Will Lillis 83fafba7f0 perf(generate): add basic free list to BitVec's arena allocator
In the naive implementation, the backing arena for `BitVec` frees no
memory, resulting in an increase of ~8-15% in peak rss. To mitigate
against this, the arena manages a simple free list, keyed by block
sizes. This keeps the reduction in wall time within 3% in the worst case
(on par for most grammars) with the naive implementation, and caps the
increase in peak rss to <1% over the current master branch.
2026-02-28 18:41:29 +01:00
Will Lillis 4d787db0b1 perf(generate): iterate set bits only in merge_token_set 2026-02-28 18:41:29 +01:00
Will Lillis 34706d3ef1 perf(generate): back BitVec with a custom arena allocator. 2026-02-28 18:41:29 +01:00
Will Lillis 8e6dea673b perf(generate): pre-alloc some BitVec sizes
We don't pre-allocate _all_ `BitVec`s, as this causes a ~20% increase in
peak rss with little to no performance gain. Certain `TokenSet`s are
guaranteed to be filled out to a fixed size, and so reserving the
space (and thus avoiding the arena waste) is safe.
2026-02-28 18:41:29 +01:00
Will Lillis 603c61fcb9 perf(generate): replace SmallBitVec with word-level BitVec for TokenSet
`TokenSet` operations (insert_all_terminals, insert_all, etc.) are called
_many_ times during parser generation. `SmallBitVec` performs these
bit-by-bit, which is incredibly slow compared to word-level operations.

Replace `SmallBitVec` with a custom `BitVec` backed by Vec<u64> that
operates at the word level.
2026-02-28 18:41:29 +01:00
dependabot[bot] f9bcce2e01 build(deps): bump rollup from 4.49.0 to 4.59.0 in /lib/binding_web
Bumps [rollup](https://github.com/rollup/rollup) from 4.49.0 to 4.59.0.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.49.0...v4.59.0)

---
updated-dependencies:
- dependency-name: rollup
  dependency-version: 4.59.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-28 15:11:12 +01:00
dependabot[bot] 3a295c7493 build(deps): bump minimatch in /lib/binding_web
Bumps  and [minimatch](https://github.com/isaacs/minimatch). These dependencies needed to be updated together.

Updates `minimatch` from 9.0.5 to 9.0.9
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v9.0.5...v9.0.9)

Updates `minimatch` from 3.1.2 to 3.1.5
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v9.0.5...v9.0.9)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-version: 9.0.9
  dependency-type: indirect
- dependency-name: minimatch
  dependency-version: 3.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-28 15:08:24 +01:00
dependabot[bot] 6cefea704e build(deps): bump minimatch from 10.2.2 to 10.2.4 in /crates/cli/eslint
Bumps [minimatch](https://github.com/isaacs/minimatch) from 10.2.2 to 10.2.4.
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v10.2.2...v10.2.4)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-version: 10.2.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-28 15:05:37 +01:00
Marian Buschsieweke a3bca3bc45
fix(wasm): pass target triple to clang (#5385)
Problem: The `clang` binary contained in the WASI-SDK releases downloaded from Github does not work on all platforms (e.g., Alpine/MUSL), but a custom (LLVM) `clang` built for the platform will default to that target, making it impossible to build wasm parsers.

Solution: Always pass `wasm32` target triple when calling clang to compile to wasm.

Notes:
* This assumes the custom `clang` is (installed or linked) to `$TREE_SITTER_WASI_SDK_PATH/bin`.
* This requires a full LLVM clang; Apple clang does not support `wasm` targets. 
* Tree-sitter expects a specified version of WASI-SDK, including a specific `clang` version. Other versions may but are not guaranteed to work.
2026-02-28 10:59:56 +01:00
MFS-code 146ea6e12b fix: skip missing Makefile in version command 2026-02-27 10:01:59 +01:00
Amaan Qureshi 0561079b6e cli(init): extract per-binding generators and named update functions
This commit just refactors the init command's update code, as it's quite messy. The main changes are that most bindings now go through their own generate_{lang} function.
2026-02-27 02:13:03 -05:00
Christian Clason 4d7d35818b build(deps): bump wasmtime to v36.0.6 2026-02-24 14:29:07 -05:00
dependabot[bot] b75abfff09 build(deps): bump anyhow from 1.0.101 to 1.0.102 in the cargo group
Bumps the cargo group with 1 update: [anyhow](https://github.com/dtolnay/anyhow).


Updates `anyhow` from 1.0.101 to 1.0.102
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.101...1.0.102)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.102
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-24 09:03:07 +01:00
Will Lillis 6ae37120b3 fix(cli): correct condition to perform __init__.py replacement for
`tree-sitter init -u` command
2026-02-23 22:53:06 -05:00
Will Lillis 5210ca0b51 fix(cli): update tree-sitter init -u for recent Package.swift
changes
2026-02-23 22:53:06 -05:00
Will Lillis f1f493e59e fix(cli): actually write updated Package.swift file 2026-02-23 22:53:06 -05:00
Amaan Qureshi 67f848cac9 loader: invalidate cached wasm toolchains on version mismatch
Previously, `get_existing_tool` checked only for the existence of
binaries in the cache directory without verifying their version, meaning bumping the version files had no effect until users manually deleted the cache directories. This commit writes a `.version` marker file after downloading and checks it on subsequent runs, removing stale caches automatically when the expected version changes.
2026-02-23 22:50:20 -05:00
Christian Clason 9b4577c6f2 feat(wasm): bump binaryen to v126 2026-02-22 17:21:20 +01:00
Christian Clason eeebf706cb feat(wasm): bump wasi-sdk to v30 2026-02-22 17:21:20 +01:00
Amaan Qureshi 787800dda6 cli(init): fix Package.swift scanner detection when used as a dependency
The template used `FileManager.default.fileExists(atPath: "src/scanner.c")` with a relative path, which resolves against the process cwd. When SwiftPM evaluates a dependency's manifest, the cwd is the consumer's directory, so the check returns `false` even when `scanner.c` exists, causing undefined symbol linker errors.

This commit uses `Context.packageDirectory`, which is available since `swift-tools-version:5.6`, to resolve the path against the package root. This does bump the minimum tools version from 5.3 to 5.6.
2026-02-22 10:55:16 -05:00
Christian Clason e62bf4ee5f ci: retrigger crates check on PR updates 2026-02-22 15:29:39 +01:00
dependabot[bot] 224598586c build(deps): bump minimatch, eslint and eslint-plugin-jsdoc
Bumps [minimatch](https://github.com/isaacs/minimatch) to 10.2.2 and updates ancestor dependencies [minimatch](https://github.com/isaacs/minimatch), [eslint](https://github.com/eslint/eslint) and [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc). These dependencies need to be updated together.


Updates `minimatch` from 3.1.2 to 10.2.2
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v10.2.2)

Updates `eslint` from 9.39.2 to 10.0.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.39.2...v10.0.1)

Updates `eslint-plugin-jsdoc` from 50.2.4 to 62.7.0
- [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases)
- [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v50.2.4...v62.7.0)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-version: 10.2.2
  dependency-type: indirect
- dependency-name: eslint
  dependency-version: 10.0.1
  dependency-type: indirect
- dependency-name: eslint-plugin-jsdoc
  dependency-version: 62.7.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-21 16:48:12 +01:00
dependabot[bot] 8e330c7f24 build(deps): bump ajv from 6.12.6 to 6.14.0 in /crates/cli/eslint
Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.12.6 to 6.14.0.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.12.6...v6.14.0)

---
updated-dependencies:
- dependency-name: ajv
  dependency-version: 6.14.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-21 16:43:21 +01:00
Will Lillis d0714e0828 docs: add basic information about creating releases 2026-02-21 15:59:32 +01:00
Will Lillis cf98b9b61e ci: add a dry-run workflow to test rust releases 2026-02-21 15:59:32 +01:00
lucasew 4e9fededff fix(lib): cast NULL in ts_subtree_children macro
Explicitly casting NULL to (Subtree *) in the ternary expression ensures
type consistency. This resolves ambiguous type inference issues encountered
by strict static analysis tools and C-to-Go transpilers (like ccgo).

Signed-off-by: lucasew <lucas59356@gmail.com>
2026-02-21 01:37:03 -05:00
Will Lillis 034780a0c9 fix(generate): unsafe extern -> extern in rendered C parser 2026-02-20 21:40:23 -05:00
Christian Clason a21ee02710 build(deps): bump wasmtime to v36.0.5 LTS
Wasmtime v34 introduced breaking ABI changes to `wasmtime_func_t` and
`wasmtime_table_t` structures. The `__private` field changed from
`size_t` to `void*`, requiring code updates to store complete
`wasmtime_func_t` structures instead of raw indices.

Changes:
- `BuiltinFunctionIndices`: `uint32_t` -> `wasmtime_func_t`
- `stdlib_fn_indices`: `uint32_t*` -> `wasmtime_func_t*`
- `FunctionDefinition.storage_location`: `uint32_t*` -> `void*`
- `get_builtin_extern()`: simplified to return stored func directly
- Lexer functions: use temp array for func storage, store table index
- Zero-initialize `builtin_fn_indices` so `store_id == 0` sentinel
  reliably detects missing stdlib exports
- Free `lexer_funcs` on error paths in `ts_wasm_store_new`
- Remove stale `(uint32_t *)` casts from `lexer_definitions`

Co-Authored-By: Amaan Qureshi <git@amaanq.com>
Co-Authored-By: nzinfo <li.monan@gmail.com>
2026-02-19 23:40:40 -05:00
Sergey Reshetnikov 5e23ccaac2 fix(lib): add RedoxOS support to portable/endian.h 2026-02-18 16:36:15 +01:00
Sergey Reshetnikov 5784924c59 use provided by RedoxOS byte orders implementations 2026-02-18 16:36:15 +01:00
Will Lillis 256df9304f rust: explicitly align CLI's encoding enum with constants from
tree_sitter::ffi
2026-02-18 01:54:20 -05:00
Will Lillis 27e1199387 feat(highlight)!: utf16 support 2026-02-18 01:54:20 -05:00
Amaan Qureshi e4aacc9a91 loader: replace fs4 with std file locking 2026-02-18 00:59:34 -05:00
Amaan Qureshi b8fcf27661 build(rust): update deps 2026-02-18 00:59:34 -05:00
Amaan Qureshi ebd2a56b5d treewide: add taplo config and reformat toml files 2026-02-18 00:59:34 -05:00
Amaan Qureshi 921af361cf xtask: fix clippy warnings from edition bump 2026-02-18 00:59:34 -05:00
Amaan Qureshi 7deb5092c6 cli: fix clippy warnings from edition bump 2026-02-18 00:59:34 -05:00
Amaan Qureshi 1c66a7f871 loader: fix clippy warnings from edition bump 2026-02-18 00:59:34 -05:00
Amaan Qureshi 1aa2fec173 tags: fix clippy warnings from edition bump 2026-02-18 00:59:34 -05:00
Amaan Qureshi 922bae62af highlight: fix clippy warnings from edition bump 2026-02-18 00:59:34 -05:00
Amaan Qureshi 26c52db75b generate: fix clippy warnings from edition bump 2026-02-18 00:59:34 -05:00
Amaan Qureshi 77c8f075f3 lib: fix clippy warnings from edition bump 2026-02-18 00:59:34 -05:00
Amaan Qureshi 0188262b4a rust: bump edition to 2024, MSRV to 1.90, and remove unnecessary allows 2026-02-18 00:59:34 -05:00
Amaan Qureshi 792bba8dab
nix: support single-checkout and multi-workspace direnv setups
The current `.envrc` unconditionally runs `use flake`, which breaks multi-workspace
jj setups where workspace subdirectories each have their own `.envrc`. Entering a
workspace unloads the root's nix env even when the workspace `.envrc` is blocked,
because direnv always unloads the parent env on any `.envrc` transition.

`source_up_if_exists` re-runs the parent `.envrc` and inherits its nix env in a
multi-workspace setup (fast due to caching). The `IN_NIX_SHELL` guard then skips
`use flake path:.`, which only runs in a plain single checkout where no parent
`.envrc` exists and `IN_NIX_SHELL` is unset.
2026-02-18 00:50:06 -05:00
dependabot[bot] 957ee4e634 ci: bump the actions group with 2 updates
Bumps the actions group with 2 updates: [actions/cache](https://github.com/actions/cache) and [actions/setup-node](https://github.com/actions/setup-node).


Updates `actions/cache` from 4 to 5
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

Updates `actions/setup-node` from 4 to 6
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-16 22:42:58 +01:00
dependabot[bot] 6952936eac build(deps): bump the cargo group with 4 updates
Bumps the cargo group with 4 updates: [cc](https://github.com/rust-lang/cc-rs), [clap](https://github.com/clap-rs/clap), [clap_complete](https://github.com/clap-rs/clap) and [ctrlc](https://github.com/Detegr/rust-ctrlc).


Updates `cc` from 1.2.55 to 1.2.56
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.55...cc-v1.2.56)

Updates `clap` from 4.5.57 to 4.5.58
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.57...clap_complete-v4.5.58)

Updates `clap_complete` from 4.5.65 to 4.5.66
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.65...clap_complete-v4.5.66)

Updates `ctrlc` from 3.5.1 to 3.5.2
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/compare/3.5.1...3.5.2)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.56
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-version: 4.5.58
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete
  dependency-version: 4.5.66
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: ctrlc
  dependency-version: 3.5.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-16 22:42:31 +01:00
dependabot[bot] f3a9b493c0 build(deps): bump the cargo group with 5 updates
Bumps the cargo group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.100` | `1.0.101` |
| [clap](https://github.com/clap-rs/clap) | `4.5.56` | `4.5.57` |
| [regex](https://github.com/rust-lang/regex) | `1.12.2` | `1.12.3` |
| [regex-syntax](https://github.com/rust-lang/regex) | `0.8.8` | `0.8.9` |
| [schemars](https://github.com/GREsau/schemars) | `1.2.0` | `1.2.1` |


Updates `anyhow` from 1.0.100 to 1.0.101
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.100...1.0.101)

Updates `clap` from 4.5.56 to 4.5.57
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.56...clap_complete-v4.5.57)

Updates `regex` from 1.12.2 to 1.12.3
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.2...1.12.3)

Updates `regex-syntax` from 0.8.8 to 0.8.9
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/regex-syntax-0.8.8...regex-syntax-0.8.9)

Updates `schemars` from 1.2.0 to 1.2.1
- [Release notes](https://github.com/GREsau/schemars/releases)
- [Changelog](https://github.com/GREsau/schemars/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GREsau/schemars/compare/v1.2.0...v1.2.1)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.101
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-version: 4.5.57
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: regex
  dependency-version: 1.12.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: regex-syntax
  dependency-version: 0.8.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: schemars
  dependency-version: 1.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-10 10:50:33 +01:00
dependabot[bot] 377c3d1c75 build(deps): bump eslint from 9.11.0 to 9.39.2 in /crates/cli/eslint
Bumps [eslint](https://github.com/eslint/eslint) from 9.11.0 to 9.39.2.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.11.0...v9.39.2)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.39.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-09 22:55:27 -05:00
Amaan Qureshi 9a78267a7b flake: update lockfile 2026-02-09 22:52:13 -05:00
Amaan Qureshi a91efd56f8 ci: cache npm packages for the web bindings 2026-02-09 22:52:13 -05:00
Amaan Qureshi e7389ccc2a make: drop redundant cargo check 2026-02-09 22:52:13 -05:00
Amaan Qureshi b1b0ef9238 ci: skip release build on PRs 2026-02-09 22:52:13 -05:00
Amaan Qureshi ad2f2ae76d ci: don't update msys2 packages 2026-02-09 22:52:13 -05:00
Amaan Qureshi 8b72e64c3c ci: replace setup-emsdk action with manual install + actions/cache 2026-02-09 22:52:13 -05:00
Amaan Qureshi 68b6020e8e ci: download pre-built wasmtime C API instead of compiling from source
ci: cache npm
2026-02-09 22:52:13 -05:00
Amaan Qureshi 4eefcef0fc ci: only run wasm tests on linux x64 and mac arm64 2026-02-09 22:52:13 -05:00
Chad McElligott 081c90b769
web: add default export to CJS bundle 2026-02-09 22:23:12 -05:00
Will Lillis 596a4d69bb fix(query): prevent cross-branch capture contamination in alternations with quantifiers
When a branch inside an alternation has a + or * quantifier, the
quantifier's pass_through step loops back to the branch's first step.
The alternation linking also sets that step's alternative_index to
point to the next branch. This causes the quantifier loop-back to
incorrectly explore other branches in the case of a failed match that
follows a successful match, contaminating captures.

This is corrected by redirecting the quantifier loop-back to a
"clean" copy of the target step without the alternative index pointing
to the next alternation branch.

Co-authored-by: Riley Bruins <ribru17@hotmail.com>
2026-02-09 21:57:29 -05:00
Jeremy Fleischman 7d3c321253 feat: allow - in grammar names
A number of grammars in the wild have this character in their name:

- https://github.com/sogaiu/tree-sitter-janet-simple/pull/7
- https://github.com/tree-sitter/tree-sitter-c-sharp/pull/408
- https://github.com/tree-sitter/tree-sitter-embedded-template/pull/45
- https://github.com/tree-sitter/tree-sitter-ql-dbscheme/pull/7

I read through https://github.com/tree-sitter/tree-sitter/pull/3700 and
https://github.com/tree-sitter/tree-sitter/issues/3637, and it doesn't
look like there was much discussion about this name regex, so hopefully
this is an acceptable change?
2026-02-09 21:28:04 -05:00
Will Lillis 48e921883a fix(rust)!: return u32 from Node::child_count
This matches the type returned from the underlying C library, and also
composes more cleanly with other functions like `Node::child`
2026-02-09 21:05:48 -05:00
Will Lillis 9dd09fec1e fix(cli): require tree-sitter CLI in CMake template 2026-02-08 18:40:57 -05:00
Amaan Qureshi 22cda59a19 lib: clean up strict aliasing fixes in array.h 2026-02-08 11:09:31 +01:00
Amaan Qureshi a234575b04 nix: fix clangd not working 2026-02-08 11:09:31 +01:00
Will Lillis 73b2f981d5 fix(cli): allow for both debug logs and graphs 2026-02-07 17:03:56 -05:00
Will Lillis c4acd92930 fix(loader): download wasm tools into a tempdir
This ensures the directories are cleaned up even in the case of an
error during or after download.
2026-02-04 22:23:11 -05:00
Will Lillis 953e3f2630 fix(test): acquire lock before downloading wasm tools
Because Rust tests run in parallel, two tests compiling a parser to wasm
can both try to download wasi-sdk and wasm-opt to a common location,
causing corrupted files and/or other failures. To prevent this, we can
guard access to these tools. `cfg(test)` isn't passed across crate
boundaries, so this lock must be present for all build configurations.
2026-02-04 22:23:11 -05:00
Bertie Wheen 39f407e3ff
fix(lib): correct ts_parser_parse docstring (#5295) 2026-02-04 00:49:54 -05:00
dependabot[bot] 73bf3febf7 build(deps): bump bytes from 1.11.0 to 1.11.1
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.11.0 to 1.11.1.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.11.0...v1.11.1)

---
updated-dependencies:
- dependency-name: bytes
  dependency-version: 1.11.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-03 22:46:35 +01:00
Will Lillis eab567a02d fix(rust): place std::env::set_var inside unsafe blocks
This is optional for the 2021 but required for 2024. The function is
unsafe either way and should be marked as such.
2026-02-02 04:29:13 -05:00
Will Lillis 2338c3d649 rust: bump language crate version
The language crate's version had to be bumped to 0.1.7 on the 0.26
release branch, so bump to 0.1.8 on master
2026-02-01 15:00:22 -05:00
Will Lillis 7e1d8add13 feat(cli): add --verbose flag to build command
This displays the working directory of the command (if present),
compiler used, all of its arguments, any environment variables set,
and anything written to stdout/stderr by the compilation tool.
2026-02-01 13:00:02 -05:00
Will Lillis 7100767f64 docs: include info on environment variables for fuzz command 2026-02-01 12:31:13 -05:00
Will Lillis 0b8f124453 fix(cli): include default values for --edits and --iterations in
`fuzz` command help
2026-02-01 12:31:13 -05:00
Will Lillis c9d9ce6cbb fix(cli): use --edits value for fuzz tests 2026-02-01 12:31:13 -05:00
Will Lillis 337e20e0f2 fix(wasm): when reallocating the last allocated region, properly grow
the region in place.

Previous changes to `malloc` caused `realloc` to sometimes pull regions
off of the free list during this optimization. Because no `memcpy` is
performed, this resulted in corrupted data returning to the caller.

Co-authored-by: trim21 <i@trim21.me>
2026-01-31 18:24:31 -05:00
Will Lillis 8fe076695f test: rename wasm corpus test "wasm_realloc"->"wasm_realloc_overflow_heap" 2026-01-31 18:24:31 -05:00
Will Lillis 439030503c fix(loader): don't rely on nm to verify scanner symbols
Relying on a user's system's installation of `nm` has proven to be bug
prone and flaky. Instead, we can enforce that these symbols are defined
by requiring the linker to resolve them. This is already the default on
macos, but linux has looser requirements which defers the error to when
the library is opened. This check was not run on Windows (because
there's no `nm`), but the msvc linker is similiarly strict to macos's
w.r.t. resolving symbols at build time rather than runtime, so there's
no issue here.
2026-01-31 17:59:50 -05:00
Will Lillis 18e2fb1e78 fix(loader): account for nm/ld fix on newer powerpc linux toolchains
Previously a bug in linux powerpc linkers/nm caused function symbols to
be incorrectly reported in the data "D" section. Newer toolchains now
correctly report these symbols' sections as "T". Account for both to
maintain compatibility with older toolchains
2026-01-31 17:59:50 -05:00
Christian Clason 69c6ce8c17 build(deps): cargo update 2026-01-31 17:51:43 -05:00
Christian Clason 1850706af0 fix(wasm): don't wasm-opt twice
Now that we run `wasm-opt` explicitly after compilation, skip it during
the `clang` compile and link phase.
2026-01-31 10:43:19 +01:00
Will Lillis 94f4143ad6 generate: return error rather than assert when ABI incompatibility is
detected
2026-01-30 22:45:07 -05:00
Will Lillis e65dc4c3b5 fix(generate): ensure parse table action id's fit within uint16_t
Without this check, action ids > 65,535 can be assigned to `uint16_t`s
in parser.c. This causes parsing to either crash or silently misbehave.
2026-01-30 22:45:07 -05:00
Will Lillis 4a2b8ed299 fix(generate): error if supertype is defined as a terminal rule 2026-01-30 22:35:41 -05:00
Will Lillis 4f8983b89f fix(ci): add wasm-stdlib.h to cache key 2026-01-27 10:25:43 +01:00
Will Lillis 7ad63a865e fix(wasm): return early from calloc if malloc fails
Co-authored-by: trim21 <i@trim21.me>
2026-01-26 21:14:14 -05:00
Will Lillis 634ba3a1f7 fix(wasm): correct several bugs in realloc
- free memory if 0 size is passed in
- Don't `memcpy` contents if new pointer is `NULL`
- Copy old region's contents only up to size of new region
- free old region

Co-authored-by: trim21 <i@trim21.me>
2026-01-26 21:14:14 -05:00
dependabot[bot] 2961ef5135 build(deps): bump the cargo group with 2 updates
Bumps the cargo group with 2 updates: [cc](https://github.com/rust-lang/cc-rs) and [thiserror](https://github.com/dtolnay/thiserror).


Updates `cc` from 1.2.53 to 1.2.54
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.53...cc-v1.2.54)

Updates `thiserror` from 2.0.17 to 2.0.18
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.17...2.0.18)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.54
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: thiserror
  dependency-version: 2.0.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 23:23:15 +00:00
Will Lillis 8816d4494f docs: document parser library symbol checks for build command 2026-01-25 18:19:32 -05:00
Will Lillis c0137208ec fix(rust): address new nightly lint 2026-01-25 17:18:43 -05:00
Will Lillis 8f4df58983 fix(init): correct paths in rust bindings on Windows 2026-01-25 17:18:43 -05:00
Will Lillis 5177b3dc26 fix(lib): address strict aliasing violations with Array type
Altering the `Array` type itself isn't feasible, as this causes
unacceptable breakage with existing parsers that depend on it. Instead,
pass in individual `Array` fields for to various `_array__*` functions.

Any time the `contents` of an array may be modified (`free`d, `realloc`d,
etc), return the potentially new address out by value. This prevents any
strict aliasing violations as we're no longer writing to a type-casted
pointer.

Co-authored-by: Nathaniel Wesley Filardo <nwfilardo@gmail.com>
2026-01-24 14:18:40 -05:00
Will Lillis 55fdc50e81 feat(xtask): allow alternate branch for fixture grammars
Set tree-sitter-php fixture to `upstream_test_fixture` branch.
Also remove the `--update` flag, as it does nothing.
2026-01-24 14:18:40 -05:00
Will Lillis d736b259de wasm: build wctype.c as part of stdlib 2026-01-24 14:19:00 +01:00
Christian Clason 397452ead5 fix(wasm): add iswpunct to stdlib 2026-01-24 14:19:00 +01:00
Will Lillis 0ab73028d6 wasm: regenerate stdlib 2026-01-24 03:04:44 -05:00
Sasha Koss ecb854da3b Add missing Wasm shims required by tree-sitter-ruby 2026-01-24 03:04:44 -05:00
Sasha Koss 400c2b5a3b Make __assert_fail static inline to avoid duplicate symbol rust-lld error 2026-01-24 03:04:44 -05:00
Will Lillis 148b4ca1b2 fix(loader): search correct cache directory for wasi-sdk clang 2026-01-23 09:10:30 +01:00
Will Lillis aea4ef2338 feat(xtask): give wasm-opt the wasi-sdk treatment 2026-01-21 21:30:13 -05:00
Will Lillis b1ec6a2586 feat(loader): use wasm-opt to optimize wasm files after compilation
Similar to wasi-sdk's `clang`, if `wasm-opt` cannot be found, binaryen
is downloaded, unzipped, and cached for future use.
2026-01-21 21:30:13 -05:00
dependabot[bot] 6739742fb6 build(deps): bump cc from 1.2.52 to 1.2.53 in the cargo group
Bumps the cargo group with 1 update: [cc](https://github.com/rust-lang/cc-rs).


Updates `cc` from 1.2.52 to 1.2.53
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.52...cc-v1.2.53)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.53
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-20 10:34:48 +01:00
dependabot[bot] d251226a3c ci: bump actions/github-script from 7 to 8 in the actions group
Bumps the actions group with 1 update: [actions/github-script](https://github.com/actions/github-script).


Updates `actions/github-script` from 7 to 8
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-20 10:34:40 +01:00
Tam1SH ae8184b8b9 docs(playground): highlight full row for highlighted nodes 2026-01-18 23:48:58 -05:00
Will Lillis 470ecf8996 feat(ci): ensure wasm-stdlib.h is regenerated when wasm stdlib source
files are modified.
2026-01-18 22:19:52 -05:00
Will Lillis 0cdb6bef7b fix(cli): warn user when nm can't be run to verify the symbols inside
the parser being built
2026-01-18 22:19:19 -05:00
theanarkh cd603fa981
feat: free memory automatically (#5225) 2026-01-18 14:39:52 -08:00
DanikVitek b12009a746 fix: Clarify/fix lifetimes
- One has to think about lifetimes if a type has one:
  - `<&'a Node<'tree>>::language` now returns `LanguageRef<'tree>` instead of
    `LanguageRef<'a>`, as it should;
- Remove explicit "outlives" requirements from `QueryMatches`, `QueryCaptures`,
  and their impl blocks, because they're inferred
- Removed unnecessary `&mut` from `cst_render_node`'s `cursor` parameter
2026-01-17 00:14:21 -05:00
DanikVitek 9f9a0bc410 fix: Renamed TreeCursor<'cursor> into TreeCursor<'tree>,
to be consistant with the usages and reduse confusion
2026-01-17 00:14:21 -05:00
Christian Clason 5d290a2a75 fix(wasm): regenerate stdlib with wasm-opt
Problem: Output of `cargo xtask build-wasm-stdlib` depends on whether
`wasm-opt` is installed (since `clang` will use it by default if it
finds it).

Solution: Install it and rerun the xtask.
2026-01-15 15:33:37 +01:00
Will Lillis 5808350bfe fix(docs): appease clippy regarding spacing in README 2026-01-15 10:38:57 +01:00
Will Lillis e64e74d5ed docs: adhere to 120 new word column limit for docs 2026-01-14 18:11:42 -05:00
Will Lillis 1a88b26a10 docs: note requirement to rebuild wasm stdlib 2026-01-14 18:11:42 -05:00
dependabot[bot] 6c05cdfb0c build(deps): bump the cargo group with 3 updates
Bumps the cargo group with 3 updates: [cc](https://github.com/rust-lang/cc-rs), [clap_complete](https://github.com/clap-rs/clap) and [serde_json](https://github.com/serde-rs/json).


Updates `cc` from 1.2.51 to 1.2.52
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.51...cc-v1.2.52)

Updates `clap_complete` from 4.5.64 to 4.5.65
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.64...clap_complete-v4.5.65)

Updates `serde_json` from 1.0.148 to 1.0.149
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.148...v1.0.149)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.52
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete
  dependency-version: 4.5.65
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: serde_json
  dependency-version: 1.0.149
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-13 10:13:03 +01:00
Will Lillis aefae11c0d fix(build): define _BSD_SOURCE
System endian conversion macros are gated behind this feature flag for
older versions of GLIBC. `_BSD_SOURCE` and `_SVID_SOURCE` were
deprecated and replaced with `_DEFAULT_SOURCE` starting with GLIBC 2.19.
2026-01-12 19:41:58 -05:00
Kevin Wang 630fa52717 fix(templates): fix python free-threading compatibility 2026-01-09 11:44:41 +02:00
dependabot[bot] eea85f4eff build(deps): bump clap from 4.5.53 to 4.5.54 in the cargo group
Bumps the cargo group with 1 update: [clap](https://github.com/clap-rs/clap).


Updates `clap` from 4.5.53 to 4.5.54
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.53...clap_complete-v4.5.54)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.54
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-07 16:42:00 +00:00
Christian Clason cd6672701b fix(wasm): update wasm-stdlib.h 2026-01-06 17:28:39 +01:00
Trim21 f4ca3d95ca
fix(wasm) add common definitions to stdlib (#5199)
Also expose `strlen` through `string.h` instead of `stdio.h`.
2026-01-06 12:01:37 +01:00
skewb1k 17e3c7a5c5 fix(cli): restore test summary output for tree-sitter test
Problem:
After commit f02d7e7e33
the `tree-sitter test` command no longer printed the final test summary,
leaving empty line. The `Stats` struct was embedded into `TestSummary`,
and the explicit call to print it was removed.

Solution:
Print `parse_stats` from `TestSummary.fmt()` implementation.
2026-01-04 21:31:11 -08:00
WillLillis dd60d5cff0 feat(cli): fill in missing fields to tree-sitter.json when running
`tree-sitter init -u`
2025-12-31 14:08:09 -05:00
WillLillis f1288ea5c9 fix(cli): increase verbosity of tree-sitter init -u updates
Also, use `info` logs rather than `warn`
2025-12-31 14:08:09 -05:00
Christian Clason 47ae060966 feat(quickjs): add console support for Array 2025-12-31 13:32:09 +01:00
Christian Clason a1893b4420 build(deps): update rquickjs to 0.11.0 2025-12-31 13:32:09 +01:00
skewb1k 999e041d49 docs: add tip about using test --update flag 2025-12-31 01:43:48 -05:00
skewb1k 0d4d854809 feat(cli): make test --update rewrite all corpus files 2025-12-31 01:43:48 -05:00
WillLillis 93d793d249 fix(cli): canonicalize build --output path
This fixes a potential issue with the new lock file hashing mechanism,
in which two different path literals pointing to the same location would
hash to separate lock files, allowing a race condition.
2025-12-30 17:07:04 +01:00
dependabot[bot] 82486d4b0a build(deps): bump the cargo group with 2 updates
Bumps the cargo group with 2 updates: [cc](https://github.com/rust-lang/cc-rs) and [serde_json](https://github.com/serde-rs/json).


Updates `cc` from 1.2.50 to 1.2.51
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.50...cc-v1.2.51)

Updates `serde_json` from 1.0.145 to 1.0.147
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.145...v1.0.147)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.51
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: serde_json
  dependency-version: 1.0.147
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-30 17:06:34 +01:00
Firas al-Khalil 5d9605a91e feat(cli): concurrent build of same grammar on different paths 2025-12-29 00:20:05 -05:00
Firas al-Khalil 5293dd683e fix(cli): report library load failure
Instead of panicking somehere else.

This happens on concurrent builds of the the same grammar.
2025-12-29 00:20:05 -05:00
Firas al-Khalil 62effdf128 fix(cli): report context on compile fail 2025-12-29 00:20:05 -05:00
WillLillis 8e4f21aba0 fix(rust): address nightly clippy lint 2025-12-27 17:05:53 -05:00
WillLillis 5208299bbb fix(cli): set language in cwd for all usages of highlight command 2025-12-27 17:05:53 -05:00
Christian Clason ba7350c7ee docs(cli): better description of files generated by init 2025-12-25 13:16:57 +01:00
skewb1k f96d518ebf fix(cli): remove extra newline with --cst
Makes CST output consistent with other formats.
2025-12-24 15:06:48 +01:00
skewb1k d5b82fbbab fix(cli): remove extra indentation with --cst --no-ranges 2025-12-24 15:06:48 +01:00
kevin-hua-kraken a7d8c0cbb2
fix(playground): update query API 2025-12-23 10:36:40 +02:00
dependabot[bot] 24007727d4 build(deps): bump the cargo group with 2 updates
Bumps the cargo group with 2 updates: [cc](https://github.com/rust-lang/cc-rs) and [clap_complete](https://github.com/clap-rs/clap).


Updates `cc` from 1.2.49 to 1.2.50
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.49...cc-v1.2.50)

Updates `clap_complete` from 4.5.61 to 4.5.62
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.61...clap_complete-v4.5.62)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.50
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete
  dependency-version: 4.5.62
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 22:18:42 +01:00
dependabot[bot] 6aa63a7213 ci: bump korthout/backport-action from 3 to 4 in the actions group
Bumps the actions group with 1 update: [korthout/backport-action](https://github.com/korthout/backport-action).


Updates `korthout/backport-action` from 3 to 4
- [Release notes](https://github.com/korthout/backport-action/releases)
- [Commits](https://github.com/korthout/backport-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: korthout/backport-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 22:18:09 +01:00
Will Lillis eacb95c85d fix(cli): correct discrepancy with cst for --no-ranges 2025-12-16 21:34:03 -05:00
dependabot[bot] 6967640571 ci: bump the actions group with 2 updates
Bumps the actions group with 2 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact).


Updates `actions/upload-artifact` from 5 to 6
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

Updates `actions/download-artifact` from 6 to 7
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 22:26:50 +01:00
skewb1k 4ac2d5d276 fix(cli): trailing whitespace after multiline text nodes in CST
Problem:
The CST printer emits trailing whitespace after multiline text nodes.
With 1704c604bf and `:cst` corpus tests
this causes trailing spaces to appear on `test --update`.
These spaces cannot be removed afterward, as the test runner
expects an exact character-for-character match for CST tests.

Solution:
Print whitespace only if node is not multiline.
2025-12-14 21:34:50 -05:00
skewb1k 642b56d9af fix(docs): remove conflicting --release cargo flag in contributing.md
The argument '--release' cannot be used with '--profile <PROFILE-NAME>'
2025-12-14 20:35:13 +01:00
Christian Clason 0574fcf256 docs(cli): include information on generated files 2025-12-14 14:26:49 +01:00
Christian Clason 98de2bc1a8 feat: start working on v0.27
* bump tree-sitter crates to 0.27.0
* bump tree-sitter-language to 0.1.7
2025-12-13 14:14:33 +01:00
Christian Clason cd4b6e2ef9 0.26.3 2025-12-13 13:41:03 +01:00
Christian Clason 8caecbc13f build(deps): cargo update 2025-12-13 12:58:59 +01:00
ObserverOfTime 1b654ae35d ci(release): use node 24 2025-12-13 06:28:18 -05:00
Marcono1234 3bd44afcaa docs(cli): fix wrong file path for Java bindings test
The test is currently generated in the default (= unnamed) package.
2025-12-10 20:54:07 +02:00
Will Lillis 8b8199775f 0.26.x
Also bump the tree-sitter-language crate to 0.1.6
2025-12-09 17:19:25 -05:00
dependabot[bot] 744e556f7e build(deps): bump esbuild
Bumps the npm group with 1 update in the /lib/binding_web directory: [esbuild](https://github.com/evanw/esbuild).


Updates `esbuild` from 0.27.0 to 0.27.1
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.0...v0.27.1)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.27.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 23:43:47 +01:00
Will Lillis 8a3dcc6155 release 0.26.1 2025-12-08 17:05:03 -05:00
dependabot[bot] b0afbf3762 build(deps): bump wasmparser from 0.242.0 to 0.243.0 in the cargo group
Bumps the cargo group with 1 update: [wasmparser](https://github.com/bytecodealliance/wasm-tools).


Updates `wasmparser` from 0.242.0 to 0.243.0
- [Release notes](https://github.com/bytecodealliance/wasm-tools/releases)
- [Commits](https://github.com/bytecodealliance/wasm-tools/commits)

---
updated-dependencies:
- dependency-name: wasmparser
  dependency-version: 0.243.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 22:38:09 +01:00
Will Lillis 974be3bb30 fix(rust): specify workspace dependency of tree-sitter-language crate
as "0.1"

If a rust project depends on both the tree-sitter lib bindings and the
language crate, cargo needs to be able to resolve a common version of
the tree-sitter-language crate. Specifying exactly "0.1.5" for the lib
bindings is overly restrictive, and could lead to future headaches. By
specifying "0.1", any "0.1.x" version should be available to resolve to.
2025-12-08 16:00:57 -05:00
ObserverOfTime d861e2bcd9 docs(cli): list Java & Zig binding files 2025-12-08 15:47:15 -05:00
ObserverOfTime b9c2d1dc89 feat(bindings): add Java bindings 2025-12-08 15:47:15 -05:00
ObserverOfTime 8ca17d1bb1 ci(release): enable trusted publishing & attestations 2025-12-08 15:38:21 -05:00
ObserverOfTime 3182efeccc feat(bindings): add byproducts to cmake 2025-12-08 04:35:09 -05:00
Will Lillis bec7c3272b fix(loader)!: correct arguments passed to select_language 2025-12-07 17:11:28 -05:00
dependabot[bot] e6bfed33ee build(deps): bump the npm group across 1 directory with 7 updates
Bumps the npm group with 7 updates in the /lib/binding_web directory:

| Package | From | To |
| --- | --- | --- |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.36.0` | `9.39.1` |
| [@types/emscripten](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/emscripten) | `1.41.2` | `1.41.5` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `24.5.2` | `24.10.1` |
| [esbuild](https://github.com/evanw/esbuild) | `0.25.10` | `0.27.0` |
| [eslint](https://github.com/eslint/eslint) | `9.36.0` | `9.39.1` |
| [tsx](https://github.com/privatenumber/tsx) | `4.20.5` | `4.21.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.44.1` | `8.48.1` |



Updates `@eslint/js` from 9.36.0 to 9.39.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v9.39.1/packages/js)

Updates `@types/emscripten` from 1.41.2 to 1.41.5
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/emscripten)

Updates `@types/node` from 24.5.2 to 24.10.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `esbuild` from 0.25.10 to 0.27.0
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.10...v0.27.0)

Updates `eslint` from 9.36.0 to 9.39.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.36.0...v9.39.1)

Updates `tsx` from 4.20.5 to 4.21.0
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.20.5...v4.21.0)

Updates `typescript-eslint` from 8.44.1 to 8.48.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.48.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.39.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: "@types/emscripten"
  dependency-version: 1.41.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@types/node"
  dependency-version: 24.10.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: esbuild
  dependency-version: 0.27.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: eslint
  dependency-version: 9.39.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: tsx
  dependency-version: 4.21.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: typescript-eslint
  dependency-version: 8.48.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-06 13:05:02 +01:00
Christian Clason 053b264502 build(deps): cargo update 2025-12-06 12:53:10 +01:00
dependabot[bot] a8f25fa441 build(deps): bump glob from 10.4.5 to 10.5.0 in /lib/binding_web
Bumps [glob](https://github.com/isaacs/node-glob) from 10.4.5 to 10.5.0.
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0)

---
updated-dependencies:
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-06 12:21:22 +01:00
dependabot[bot] f450ce4f6e build(deps): bump vite from 7.1.5 to 7.1.11 in /lib/binding_web
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.5 to 7.1.11.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.11/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.11
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-05 23:59:37 -05:00
dependabot[bot] 3ff8edf9e8 build(deps): bump js-yaml from 4.1.0 to 4.1.1 in /crates/cli/eslint
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.1.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-05 22:25:23 -05:00
dependabot[bot] 6b6040961c build(deps): bump js-yaml from 4.1.0 to 4.1.1 in /lib/binding_web
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.1.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-05 22:24:47 -05:00
Will Lillis 888f57657d fix(cli): improve error reporting for invalid range arguments to query
command
2025-12-03 18:06:16 -05:00
Max Brunsfeld be8fe690d8 Clean up node range tracking in query_cursor__advance 2025-12-03 18:06:16 -05:00
Piotr Osiewicz c0b1710f8a Add containing range APIs to query cursor
Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: dino <dinojoaocosta@gmail.com>
Co-authored-by: John Tur <john-tur@outlook.com>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: dino <dinojoaocosta@gmail.com>
Co-authored-by: Will Lillis <will.lillis24@gmail.com>
2025-12-03 18:06:16 -05:00
Piotr Osiewicz 7d3feeae9a cli: Do not validate UTF-8 boundaries when query results are not being
tested

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
Co-authored-by: dino <dinojoaocosta@gmail.com>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: John Tur <john-tur@outlook.com>
2025-12-03 18:06:16 -05:00
dependabot[bot] 3f85f65e3f build(deps): bump the cargo group with 2 updates
Bumps the cargo group with 2 updates: [cc](https://github.com/rust-lang/cc-rs) and [wasmparser](https://github.com/bytecodealliance/wasm-tools).


Updates `cc` from 1.2.47 to 1.2.48
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.47...cc-v1.2.48)

Updates `wasmparser` from 0.241.2 to 0.242.0
- [Release notes](https://github.com/bytecodealliance/wasm-tools/releases)
- [Commits](https://github.com/bytecodealliance/wasm-tools/commits)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.48
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: wasmparser
  dependency-version: 0.242.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-02 08:10:34 +01:00
WillLillis df8b62fc50 feat(xtask): bring wasi-sdk treatment up to par with the loader
The loader package's `ensure_wasi_sdk_exists` private method checks for
the wasi-sdk, fetching it if it can't be found. This logic was
re-implemented in xtask for `build-wasm-stdlib`, but without the
fetching functionality. We can have nice things in xtask too! Rather
than make this function a public member of `tree-sitter-loader`, we
just re-implement and leave a nice comment asking people to keep the
two in sync.
2025-11-27 16:06:25 -05:00
ObserverOfTime 14b4708018 fix(loader): write Wasm lib directly to lib dir
Problem: `fs::rename` fails if the parser directory and the Tree-sitter
library directory are on different file systems.

Solution: Write the library file directly to the final directory.
2025-11-25 17:23:44 +01:00
WillLillis dcef0cc0ee fix(cli): correct query match limit warning condition 2025-11-25 17:23:19 +01:00
WillLillis c1a0f48781 fix(cli): return error if --wasm flag is passed when the wasm feature
is disabled

This applies to the `parse` and `test` commands, but not `build` as it
doesn't require the wasm feature. Also, hide the `--wasm` options if
from the `--help` output if the feature is disabled.
2025-11-25 17:23:19 +01:00
Antonin Delpeuch f6d17fdb04 fix(node): bump tree-sitter dep to 0.25 in bindings
Sets the dependency `tree-sitter` to version 0.25 in
the NodeJS bindings generated by default, so that
`npm run test` passes.
2025-11-25 03:32:31 -05:00
Riley Bruins 829733a35e fix(query): prevent infinite loop with + and ? quantifiers
**Problem:** A query with a `?` quantifier followed by a `+` quantifier
would hang at 100% CPU usage while iterating through a tree, regardless
of the source content.

**Solution:** Collect all quantifiers in one step, and then add the
required repeat/optional step logic *after* we have determined the
composite quantifier we need to use for the current step.
2025-11-25 03:21:13 -05:00
Christian Clason d64b863030 build(deps): bump wasi-sdk to v29 2025-11-25 07:41:04 +01:00
skewb1k 882aa867eb docs: remove manual bindings update steps for scanner
Since 66dab20462, bindings automatically
detect external scanner, making the instructions for manual updating
outdated. Avoids confusion about missing commented lines in Rust
bindings.
2025-11-24 23:00:40 +01:00
dependabot[bot] de92a9b4c9 build(deps): bump the cargo group with 4 updates
Bumps the cargo group with 4 updates: [cc](https://github.com/rust-lang/cc-rs), [clap](https://github.com/clap-rs/clap), [clap_complete](https://github.com/clap-rs/clap) and [indexmap](https://github.com/indexmap-rs/indexmap).


Updates `cc` from 1.2.46 to 1.2.47
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.46...cc-v1.2.47)

Updates `clap` from 4.5.51 to 4.5.53
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.51...clap_complete-v4.5.53)

Updates `clap_complete` from 4.5.60 to 4.5.61
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.60...clap_complete-v4.5.61)

Updates `indexmap` from 2.12.0 to 2.12.1
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.12.0...2.12.1)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.47
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-version: 4.5.53
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete
  dependency-version: 4.5.61
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: indexmap
  dependency-version: 2.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 22:42:26 +01:00
dependabot[bot] 5880df47e2 ci: bump actions/checkout from 5 to 6 in the actions group
Bumps the actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 5 to 6
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 22:41:27 +01:00
Will Lillis e92a7803eb fix(docs): final updates before 0.26.1
- Indicate where xtask looks for wasi-sdk
- Indicate where `build --wasm` looks for and downloads wasi-sdk binary
  to
- Mark native runtime as experimental, describe limitations
- Note ABI 13 support limitations
- Mention that `test --wasm` and `parse --wasm` require
  `--features=wasm` build
2025-11-24 15:18:12 +01:00
Will Lillis 0d656de98b feat(cli): update zig bindings with version command 2025-11-21 19:40:14 -05:00
Amaan Qureshi b095968dff refactor(cli): clean up version updating code
This commit adds proper error types when updating the version across
files
2025-11-21 19:40:14 -05:00
Will Lillis d592b16ac0 fix(docs): list dependencies on external tooling for version command 2025-11-21 19:40:14 -05:00
Will Lillis 320c0865e9 feat(cli): don't bail after first version update fails 2025-11-21 19:40:14 -05:00
ObserverOfTime 60635e0729 fix(generate): add node_modules to quickjs resolver 2025-11-21 19:39:53 -05:00
Antonin Delpeuch 120f74723e docs: fix typo in the page about ABI version
Of course I only catch that once they are already published…
2025-11-20 09:58:07 +01:00
Antonin Delpeuch 02508d5570 Apply suggestions from code review
Co-authored-by: Christian Clason <ch.clason+github@icloud.com>
2025-11-20 01:07:58 -05:00
Antonin Delpeuch 42e7e9c3e7 Integrate rewording suggestions 2025-11-20 01:07:58 -05:00
Antonin Delpeuch 55b9a25c84 docs: New page about ABI versions for parser users
Closes #374.

The statement about the intended backwards compatibility is purely
speculative and provided as a "straw man" to help reviewers come up with
a better description of the intended backwards compatibility.
2025-11-20 01:07:58 -05:00
Will Lillis 877782a8a4 fix(docs): update cli docs to reflect changes to various subcommand
arguments
2025-11-19 04:30:01 -05:00
Antonin Delpeuch 0e1f715ef1 Move PathsJSON method, reformat 2025-11-19 03:57:13 -05:00
Antonin Delpeuch f3012a999d feat(bindings): expose the queries dynamically
Available in the Rust, Python, and Node bindings

Co-authored-by: ObserverOfTime <chronobserver@disroot.org>
2025-11-19 03:57:13 -05:00
dependabot[bot] 3072d35ed5 build(deps): bump the cargo group with 2 updates
Bumps the cargo group with 2 updates: [cc](https://github.com/rust-lang/cc-rs) and [wasmparser](https://github.com/bytecodealliance/wasm-tools).


Updates `cc` from 1.2.45 to 1.2.46
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.45...cc-v1.2.46)

Updates `wasmparser` from 0.240.0 to 0.241.2
- [Release notes](https://github.com/bytecodealliance/wasm-tools/releases)
- [Commits](https://github.com/bytecodealliance/wasm-tools/commits)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.46
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: wasmparser
  dependency-version: 0.241.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-17 22:29:19 +01:00
Valeriy Kosikhin 57e3a7b2ca fix(loader): set correct runtime host for cc while cross-compiling
Pass the BUILD_TARGET variable from the build environment as 'host' for
the cc crate. Otherwise, when cross-compiled, cc will keep looking for a
cross-compiler instead of the native one on the target system.

Signed-off-by: Valeriy Kosikhin <vkosikhin@gmail.com>
2025-11-17 18:19:34 +01:00
Christian Clason 0df2916920 bulild(deps): cargo update 2025-11-14 11:28:00 +01:00
WillLillis 61c21aa408 refactor(generate)!: include path when available in IO errors 2025-11-14 11:28:00 +01:00
WillLillis 7eb23d9f3c refactor(config)!: transition from anyhow to thiserror 2025-11-14 11:28:00 +01:00
WillLillis db2d221ae9 fix(generate): remove leftover imports of anyhow 2025-11-14 11:28:00 +01:00
WillLillis 67cb3cb881 refactor(loader)!: transition from anyhow to thiserror 2025-11-14 11:28:00 +01:00
Marcono1234 12a31536e1 fix(docs): don't show mdbook help popup when using query editor 2025-11-12 23:43:03 -05:00
Will Lillis 7657cc9d35 fix(dsl): add ReservedRule to Rule type definition 2025-11-12 07:27:05 +01:00
dependabot[bot] 13ff3935ac build(deps): bump the cargo group with 3 updates
Bumps the cargo group with 3 updates: [cc](https://github.com/rust-lang/cc-rs), [libloading](https://github.com/nagisa/rust_libloading) and [schemars](https://github.com/GREsau/schemars).


Updates `cc` from 1.2.44 to 1.2.45
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.44...cc-v1.2.45)

Updates `libloading` from 0.8.9 to 0.9.0
- [Commits](https://github.com/nagisa/rust_libloading/compare/0.8.9...0.9.0)

Updates `schemars` from 1.0.4 to 1.0.5
- [Release notes](https://github.com/GREsau/schemars/releases)
- [Changelog](https://github.com/GREsau/schemars/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GREsau/schemars/compare/v1.0.4...v1.0.5)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.45
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: libloading
  dependency-version: 0.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
- dependency-name: schemars
  dependency-version: 1.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-10 23:04:54 +01:00
Will Lillis 361287fb56 fix(cli)!: deprecate --build flag for generate command 2025-11-07 08:51:24 +01:00
dependabot[bot] 13d4db8bb4 build(deps): bump the cargo group across 1 directory with 6 updates
Bumps the cargo group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.43` | `1.2.44` |
| [clap](https://github.com/clap-rs/clap) | `4.5.50` | `4.5.51` |
| [clap_complete](https://github.com/clap-rs/clap) | `4.5.59` | `4.5.60` |
| [clap_complete_nushell](https://github.com/clap-rs/clap) | `4.5.9` | `4.5.10` |
| [etcetera](https://github.com/lunacookies/etcetera) | `0.10.0` | `0.11.0` |
| [wasmparser](https://github.com/bytecodealliance/wasm-tools) | `0.229.0` | `0.240.0` |



Updates `cc` from 1.2.43 to 1.2.44
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.43...cc-v1.2.44)

Updates `clap` from 4.5.50 to 4.5.51
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.50...clap_complete-v4.5.51)

Updates `clap_complete` from 4.5.59 to 4.5.60
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.59...clap_complete-v4.5.60)

Updates `clap_complete_nushell` from 4.5.9 to 4.5.10
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete_nushell-v4.5.9...clap_complete_nushell-v4.5.10)

Updates `etcetera` from 0.10.0 to 0.11.0
- [Release notes](https://github.com/lunacookies/etcetera/releases)
- [Commits](https://github.com/lunacookies/etcetera/compare/v0.10.0...v0.11.0)

Updates `wasmparser` from 0.229.0 to 0.240.0
- [Release notes](https://github.com/bytecodealliance/wasm-tools/releases)
- [Commits](https://github.com/bytecodealliance/wasm-tools/commits)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.44
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-version: 4.5.51
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete
  dependency-version: 4.5.60
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete_nushell
  dependency-version: 4.5.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: etcetera
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
- dependency-name: wasmparser
  dependency-version: 0.240.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-04 16:08:54 +01:00
Will Lillis 419a5a7305 fix(generate): don't short-circuit within extend_sorted 2025-11-03 01:22:29 -05:00
Will Lillis c7b5f89392 feat(xtask): generate JSON schema for cli TestSummary 2025-11-02 21:08:55 -05:00
Will Lillis d546e28abf fix(cli): mark report_states_for_rule and json/json_summary flags
for `generate` command as conflicting
2025-11-02 21:08:55 -05:00
WillLillis 86e2fd2337 fix(cli): correct behavior of parse --stat and --json-summary flags 2025-11-02 21:08:55 -05:00
WillLillis ff255a2354 test: add coverage for new test aggregation method 2025-11-02 21:08:55 -05:00
WillLillis fe67521b3d refactor(cli)!: deprecate json flags in favor of json-summary 2025-11-02 21:08:55 -05:00
WillLillis f02d7e7e33 feat(test): display test results in JSON format 2025-11-02 21:08:55 -05:00
WillLillis 6a8676f335 refactor(test): generalize printing of test diff keys and diffs 2025-11-02 21:08:55 -05:00
WillLillis 944386d25f refactor(test): clean up test filtering logic
Also, only update the expected output of a case when it is skipped if
the `update` flag has been passed
2025-11-02 21:08:55 -05:00
Will Lillis ef03a3f8fe fix(ci): correct mdbook release url 2025-11-02 18:37:09 -05:00
Christian Clason 18a5243933 ci(docs): pin mdbook to latest release 2025-11-02 18:07:47 -05:00
Christian Clason 8444cc3deb fix(docs): remove multilingual config field
Problem: "deploy docs" always pulls in the `latest` release of `mdbook`,
which now is a v0.5.0 prerelease with breaking changes -- including
removing an (apparently unused) `multilingual` config field in the TOML
that is now an error (another breaking change).

Solution: Delete the line. Add `workflow_dispatch` to the docs workflow
in case follow-up changes are needed; see
https://github.com/rust-lang/mdBook/blob/master/CHANGELOG.md#05-migration-guide
2025-11-02 18:07:47 -05:00
Will Lillis 097c2d4f05 fix(cli): remove --emit=lib generate option
This also replaces the `--emit` option with an `--no-parser` flag. The
default value is false, meaning a parser is still generated by default.
2025-11-02 10:26:28 +01:00
WillLillis b8f52210f9 perf: reduce needless allocations 2025-10-30 18:24:42 +01:00
WillLillis ecc787e221 fix(test): correct language typo in test name 2025-10-30 18:24:42 +01:00
Christian Clason 6188010f53 build(deps): bump rquickjs to v0.10.0 2025-10-29 18:30:25 -04:00
Christian Clason 70cde4a110 ci(dependabot): only update patch releases for cargo 2025-10-29 18:30:25 -04:00
Christian Clason 77363a65c2 build(deps): cargo update 2025-10-29 18:30:25 -04:00
dependabot[bot] 605e580063 ci: bump the actions group across 1 directory with 3 updates
Bumps the actions group with 3 updates in the / directory: [actions/upload-artifact](https://github.com/actions/upload-artifact), [actions/download-artifact](https://github.com/actions/download-artifact) and [actions/setup-node](https://github.com/actions/setup-node).


Updates `actions/upload-artifact` from 4 to 5
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

Updates `actions/download-artifact` from 5 to 6
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v5...v6)

Updates `actions/setup-node` from 5 to 6
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/download-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-28 09:23:50 +01:00
WillLillis a2f2b16acb fix(xtask): require version argument for bump-version command 2025-10-24 18:13:59 -04:00
Will Lillis 87d778a1c6 fix(rust): apply Self usage in struct definition lint 2025-10-24 17:50:28 -04:00
Will Lillis e344837e35 fix(rust): minor cleanup in generate code 2025-10-24 17:50:28 -04:00
ObserverOfTime bdee2c2dd3 ci: use macos-15-intel runner
The macos-13 runner will soon be removed.
2025-10-24 10:14:52 +02:00
dependabot[bot] da5926d6f5 build(deps): bump the cargo group across 1 directory with 4 updates
Bumps the cargo group with 4 updates in the / directory: [anstyle](https://github.com/rust-cli/anstyle), [cc](https://github.com/rust-lang/cc-rs), [thiserror](https://github.com/dtolnay/thiserror) and [widestring](https://github.com/VoidStarKat/widestring-rs).


Updates `anstyle` from 1.0.11 to 1.0.13
- [Commits](https://github.com/rust-cli/anstyle/compare/v1.0.11...v1.0.13)

Updates `cc` from 1.2.39 to 1.2.41
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.39...cc-v1.2.41)

Updates `thiserror` from 2.0.16 to 2.0.17
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.16...2.0.17)

Updates `widestring` from 1.2.0 to 1.2.1
- [Release notes](https://github.com/VoidStarKat/widestring-rs/releases)
- [Changelog](https://github.com/VoidStarKat/widestring-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/VoidStarKat/widestring-rs/compare/v1.2.0...v1.2.1)

---
updated-dependencies:
- dependency-name: anstyle
  dependency-version: 1.0.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: cc
  dependency-version: 1.2.41
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: thiserror
  dependency-version: 2.0.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: widestring
  dependency-version: 1.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-14 11:00:03 +02:00
Will Lillis b3bc7701cd refactor(generate): make AliasMap use BTreeMap over HashMap 2025-10-12 15:56:30 -04:00
Will Lillis 262f1782cc fix(generate): ensure deterministic iteration order for symbol aliases
while constructing node-types.json
2025-10-12 15:56:30 -04:00
WillLillis 00d172bf9f fix(generate): correct display of precedence for
`--report-states-for-rule`
2025-10-12 15:56:12 -04:00
Will Lillis ae54350c76 fix(generate): Add missing fields to NodeInfoJson sorting
This ensures a deterministic ordering for node-types.json
2025-10-11 14:25:52 -04:00
Will Lillis 3355825a68 fix(cli): don't load languages for build command 2025-10-07 17:21:09 -04:00
ObserverOfTime 7d0e029e37 chore: add schema for node-types.json 2025-10-05 09:57:23 +03:00
Mihai-Daniel Potirniche 0f5ccc4aba Fix typo 2025-10-04 12:12:39 -04:00
ObserverOfTime 0cf6e7c507 fix(cli): prevent crash when parsing stdin
When we are parsing stdin via a pipe or heredoc, the source count is 0
(unsigned) so the XML output crashes while trying to subtract from it.
2025-10-03 19:24:58 +03:00
dependabot[bot] 1dc4804b6e build(deps): bump the cargo group with 2 updates
Bumps the cargo group with 2 updates: [memchr](https://github.com/BurntSushi/memchr) and [regex](https://github.com/rust-lang/regex).


Updates `memchr` from 2.7.5 to 2.7.6
- [Commits](https://github.com/BurntSushi/memchr/compare/2.7.5...2.7.6)

Updates `regex` from 1.11.2 to 1.11.3
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.11.2...1.11.3)

---
updated-dependencies:
- dependency-name: memchr
  dependency-version: 2.7.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: regex
  dependency-version: 1.11.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-01 02:57:21 -04:00
Amaan Qureshi c5b22a1dc6 ci: split cross compilation and emscripten tag read 2025-10-01 02:57:04 -04:00
Amaan Qureshi 92efd26380 fix(loader): allow parallel compilation on windows 2025-10-01 02:57:04 -04:00
Amaan Qureshi 24c8feba3e fix(bindings): fix root detection on windows 2025-09-28 08:40:02 -04:00
Amaan Qureshi 122493b717 style(cli): appease clippy 2025-09-28 05:54:28 -04:00
Amaan Qureshi 4edcca9850 style(loader): appease clippy 2025-09-28 05:54:28 -04:00
dependabot[bot] be0c44f871 build(deps): bump the cargo group with 7 updates
Bumps the cargo group with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.99` | `1.0.100` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.37` | `1.2.39` |
| [clap](https://github.com/clap-rs/clap) | `4.5.47` | `4.5.48` |
| [clap_complete](https://github.com/clap-rs/clap) | `4.5.57` | `4.5.58` |
| [indexmap](https://github.com/indexmap-rs/indexmap) | `2.11.1` | `2.11.4` |
| [libloading](https://github.com/nagisa/rust_libloading) | `0.8.8` | `0.8.9` |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.22.0` | `3.23.0` |


Updates `anyhow` from 1.0.99 to 1.0.100
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.99...1.0.100)

Updates `cc` from 1.2.37 to 1.2.39
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.37...cc-v1.2.39)

Updates `clap` from 4.5.47 to 4.5.48
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.47...clap_complete-v4.5.48)

Updates `clap_complete` from 4.5.57 to 4.5.58
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.57...clap_complete-v4.5.58)

Updates `indexmap` from 2.11.1 to 2.11.4
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.11.1...2.11.4)

Updates `libloading` from 0.8.8 to 0.8.9
- [Commits](https://github.com/nagisa/rust_libloading/compare/0.8.8...0.8.9)

Updates `tempfile` from 3.22.0 to 3.23.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.22.0...v3.23.0)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.100
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: cc
  dependency-version: 1.2.39
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-version: 4.5.48
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete
  dependency-version: 4.5.58
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: indexmap
  dependency-version: 2.11.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: libloading
  dependency-version: 0.8.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: tempfile
  dependency-version: 3.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-28 05:54:28 -04:00
ObserverOfTime 35b1356e96 ci(dependabot): update package.json as well 2025-09-28 04:15:36 -04:00
ObserverOfTime 443acf080a ci(dependabot): enable cooldown period
This setting will delay package updates by 3 days which generally
should be enough time for supply chain attacks to be discovered
2025-09-28 03:58:23 -04:00
Amaan Qureshi 00e394f0f1 feat(lib)!: disallow whitespace in supertype syntax 2025-09-28 00:56:30 -04:00
Amaan Qureshi 341665824c fix(lib): validate subtypes in supertype queries 2025-09-28 00:56:30 -04:00
Amaan Qureshi bd02be25d5 fix(lib): allow anonymous nodes in the supertype query syntax 2025-09-28 00:56:30 -04:00
WillLillis 12a6400c63 fix(test): trim trailing carriage return unconditionally in test
contents
2025-09-27 19:28:03 -04:00
ObserverOfTime d86e1b4f5e feat(bindings): generate zig fingerprint 2025-09-26 18:02:35 -04:00
WillLillis 422866a437 fix(docs): update more broken links 2025-09-26 16:10:29 -05:00
Amaan Qureshi 5f7806f99e feat: add option to disable parse state optimizations 2025-09-26 02:40:53 -04:00
WillLillis a9bce7c18a fix(generate): return error when generated grammar's state count exceeds
the maximum allowed value.

Co-authored-by: Amaan Qureshi <git@amaanq.com>
2025-09-25 22:29:04 -05:00
WillLillis 335bfabc60 feat(cli): include filenames in parsing xml output 2025-09-25 22:28:31 -05:00
Amaan Qureshi e1b424c191 Revert "0.26.0" 2025-09-24 16:42:01 -04:00
Amaan Qureshi ea9c318afb docs: update highlight crate link 2025-09-24 16:29:08 -04:00
ObserverOfTime 9d66dbc28f chore: remove CARGO_WORKSPACE_DIR var 2025-09-24 16:14:08 -04:00
Riley Bruins 8c22426223
feat(rust): add new_raw to create a raw, unchecked query pointer 2025-09-23 19:06:22 -04:00
Amaan Qureshi 90ee433c9b fix(lib): account for unreachable patterns with children
Co-authored-by: Will Lillis <will.lillis24@gmail.com>
2025-09-23 17:17:45 -04:00
Amaan Qureshi f26bd44a43 flake: remove cross, add llvm-cov support 2025-09-23 01:19:14 -04:00
Amaan Qureshi 021d9c447d test: clean up async boundary test 2025-09-23 01:19:14 -04:00
Amaan Qureshi ce56465197 test(rust): prefer asserts to panics 2025-09-23 01:19:14 -04:00
Amaan Qureshi b0cdab85fe refactor(rust): avoid panics where possible 2025-09-23 01:19:14 -04:00
Amaan Qureshi 47c9256976 test: clean up parser hang test 2025-09-23 01:19:14 -04:00
dependabot[bot] cf89840460 build(deps): bump the npm group across 1 directory with 6 updates
Bumps the npm group with 6 updates in the /lib/binding_web directory:

| Package | From | To |
| --- | --- | --- |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.35.0` | `9.36.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `24.5.0` | `24.5.2` |
| [esbuild](https://github.com/evanw/esbuild) | `0.25.9` | `0.25.10` |
| [eslint](https://github.com/eslint/eslint) | `9.35.0` | `9.36.0` |
| [typescript](https://github.com/microsoft/TypeScript) | `5.8.3` | `5.9.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.44.0` | `8.44.1` |



Updates `@eslint/js` from 9.35.0 to 9.36.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.36.0/packages/js)

Updates `@types/node` from 24.5.0 to 24.5.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `esbuild` from 0.25.9 to 0.25.10
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.9...v0.25.10)

Updates `eslint` from 9.35.0 to 9.36.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.35.0...v9.36.0)

Updates `typescript` from 5.8.3 to 5.9.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.8.3...v5.9.2)

Updates `typescript-eslint` from 8.44.0 to 8.44.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.44.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.36.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: "@types/node"
  dependency-version: 24.5.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: esbuild
  dependency-version: 0.25.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: eslint
  dependency-version: 9.36.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: typescript
  dependency-version: 5.9.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: typescript-eslint
  dependency-version: 8.44.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-22 21:49:46 -04:00
Max Brunsfeld e3294c3faf
build: bump tree-sitter-language to 0.1.5 2025-09-22 15:54:15 -04:00
Amaan Qureshi 95ab17e444 build: define _DARWIN_C_SOURCE 2025-09-22 19:50:43 +03:00
Max Brunsfeld 9b914885f1
Fix issues preventing releases from successfully publishing (#4867)
* Correct the path to the CLI npm package in release job

* Specify a version for tree-sitter-language

* Fix path to README in doc include
2025-09-22 09:24:30 -07:00
WillLillis 92678f0fc5 fix(rust): pass correct fd to C lib's ts_tree_print_dot_graph
Co-authored-by: Amaan Qureshi <git@amaanq.com>
2025-09-21 18:21:57 -04:00
ObserverOfTime a1640e4fe4 chore: rebuild wasm stdlib 2025-09-21 16:28:08 -04:00
ObserverOfTime 1be51c2129 chore: upgrade emscripten to 4.0.15 2025-09-21 16:28:08 -04:00
Will Lillis 6214f95e7e
docs: correct new generate flag: "stage"->"emit" 2025-09-21 15:22:11 -04:00
Amaan Qureshi c89e40f008 fix(generate): fix builds outside of crate workspace 2025-09-21 02:34:10 -04:00
Antonin Delpeuch 8873c1aeff docs: update options for generate command 2025-09-21 01:53:40 -04:00
Amaan Qureshi d543e2e50b refactor(loader): use the logger 2025-09-21 01:53:22 -04:00
Amaan Qureshi e5c11d9efc refactor(config): use the logger 2025-09-21 01:53:22 -04:00
ObserverOfTime d13657c40c refactor(generate): use the logger
Co-authored-by: Amaan Qureshi <git@amaanq.com>
2025-09-21 01:53:22 -04:00
ObserverOfTime 804ef22075 refactor(cli): use the logger
Co-authored-by: Amaan Qureshi <git@amaanq.com>
2025-09-21 01:53:22 -04:00
Amaan Qureshi fa28b430af feat(bindings): update Zig bindings to use Language.fromRaw 2025-09-21 01:42:26 -04:00
Amaan Qureshi 311585d304 refactor!: rename stage flag to emit 2025-09-20 22:35:23 -04:00
ObserverOfTime 60c3bed6a4 fix(loader): install arm64 wasi-sdk on arm64 windows 2025-09-20 18:11:48 -04:00
ObserverOfTime 6dfa79013f feat(loader): respect NM env var 2025-09-20 18:08:07 -04:00
ObserverOfTime 79ef484392 ci: cross-compile without cross-rs 2025-09-20 18:08:07 -04:00
Amaan Qureshi 0ca8fe8c12 feat(playground): add export flag 2025-09-19 17:55:28 -04:00
John-Philip Taylor 552ab537e8
docs(cli): add docs on new version features 2025-09-19 17:52:08 -04:00
Amaan Qureshi 0cf217179c feat(rust): add reborrow method to ParseOptions 2025-09-19 17:40:35 -04:00
Amaan Qureshi 48a5077035 feat(web)!: add API for editing points and ranges 2025-09-19 17:40:26 -04:00
Amaan Qureshi a69367f739 feat: add API for editing points and ranges 2025-09-19 17:40:26 -04:00
Will Lillis 1a0868c487 build: add static and shared targets to Makefile 2025-09-19 17:06:27 -04:00
Max Brunsfeld 9be3e2bdd8 0.26.0 2025-09-19 08:14:00 -07:00
Amaan Qureshi 074e991280 refactor: deduplicate allocation code in tests 2025-09-18 20:29:54 -04:00
Nia f09dc3cf46
fix(wasm): fix alias map size computation
This fixes a crash where parsing with certain languages can lead to a crash due to how the alias map was allocated and laid out in wasm memory
2025-09-18 18:34:27 -04:00
ObserverOfTime f222db57ce fix(bindings): fix ESM errors in Node bindings
1. The module tries to call the native binary as a function.

Only `node-gyp-build` returns a function, so the call is moved there.

2. `node-types.json` is imported with outdated syntax.

Use import attributes which require Node 18.

3. The test does not properly catch import errors.

This is solved by moving the import inside the assertion.
2025-09-18 16:59:34 -04:00
Amaan Qureshi d29132512b ci: build wasm32 2025-09-17 04:57:49 -04:00
Amaan Qureshi 22553b3372 feat: support compiling to wasm32-unknown-unknown 2025-09-17 04:57:49 -04:00
Will Lillis 46ea65c89b refactor: remove url dependency 2025-09-17 04:31:53 -04:00
Will Lillis 6a28a62369 test: add safety checks to ensure langauge version constants are kept in
sync

The generate crate defines the `LANGUAGE_VERSION` constant separately
from the TREE_SITTER_LANGUAGE_VERSION definition in `api.h`.
2025-09-17 02:58:31 -04:00
Amaan Qureshi db0d05fab3 ci: fix cache paths 2025-09-17 02:19:29 -04:00
Amaan Qureshi 317e2e74c2 Revert "feat(generate): allow more characters for keywords"
This reverts commit 0269357c5a.
2025-09-17 02:19:29 -04:00
Amaan Qureshi 04cfee5664 build(rust): remove unused dependencies 2025-09-16 18:57:06 -04:00
Amaan Qureshi 57c6105897 fix(generate): remove warning message for CJS grammars 2025-09-16 16:42:17 -04:00
Christian Clason 339bad2de4 feat(generate): don't embed tree-sitter CLI version in parser
Problem: embedding the CLI version used to generate a parser triggers CI
failures on all grammars for every (patch) release of tree-sitter, even
if there are no actual parser changes.

Solution: do not embed the version; instead rely on whether the update
introduces actual (presumably desirable) changes in the parser to
indicate regeneration is necessary.
2025-09-16 19:21:34 +02:00
Will Lillis 31ff62445b fix(generate): assert there is a Nfa last state before retrieving it
Prevents unsigned subtraction wrapping antics in release builds
2025-09-16 03:51:13 -04:00
Will Lillis c54bc441ba fix(test): include failing test's path in error message
This helps when an empty folder was left behind in the corpus directory
2025-09-16 03:51:13 -04:00
Will Lillis 070b91628f fix(rust): appease clippy 2025-09-16 03:51:13 -04:00
bbb651 9593737871 build(generate): remove tree-sitter dependency
It was only used to share two constants, and balloons its dependencies.

This also makes `generate_parser_for_grammar` work in wasm.
(Tested in `wasm32-wasip2` in wasmtime with the json grammar,
`wasm32-unknown-unknown` running in the same setup exited successfully
so I'm pretty confident it works as well)

Co-authored-by: Amaan Qureshi <contact@amaanq.com>
2025-09-16 03:48:30 -04:00
Amaan Qureshi 0269357c5a feat(generate): allow more characters for keywords 2025-09-16 03:01:56 -04:00
Amaan Qureshi fd68c02072 fix(init): add missing quote in replacement 2025-09-16 02:24:11 -04:00
Amaan Qureshi 39a67eec61 feat: migrate to ESM 2025-09-16 02:24:11 -04:00
Amaan Qureshi 67f50b85f5 docs: document the native js runtime 2025-09-16 02:24:11 -04:00
Amaan Qureshi eedbec8f24 feat: remove the need of an external JS runtime for processing grammars 2025-09-16 02:24:11 -04:00
dependabot[bot] 7ba7c4a8ce build(deps): bump the cargo group with 7 updates
Bumps the cargo group with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.36` | `1.2.37` |
| [indexmap](https://github.com/indexmap-rs/indexmap) | `2.11.0` | `2.11.1` |
| [semver](https://github.com/dtolnay/semver) | `1.0.26` | `1.0.27` |
| [serde](https://github.com/serde-rs/serde) | `1.0.219` | `1.0.224` |
| [serde_derive](https://github.com/serde-rs/serde) | `1.0.219` | `1.0.224` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.143` | `1.0.145` |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.21.0` | `3.22.0` |


Updates `cc` from 1.2.36 to 1.2.37
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.36...cc-v1.2.37)

Updates `indexmap` from 2.11.0 to 2.11.1
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.11.0...2.11.1)

Updates `semver` from 1.0.26 to 1.0.27
- [Release notes](https://github.com/dtolnay/semver/releases)
- [Commits](https://github.com/dtolnay/semver/compare/1.0.26...1.0.27)

Updates `serde` from 1.0.219 to 1.0.224
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.219...v1.0.224)

Updates `serde_derive` from 1.0.219 to 1.0.224
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.219...v1.0.224)

Updates `serde_json` from 1.0.143 to 1.0.145
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.143...v1.0.145)

Updates `tempfile` from 3.21.0 to 3.22.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.21.0...v3.22.0)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.37
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: indexmap
  dependency-version: 2.11.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: semver
  dependency-version: 1.0.27
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: serde
  dependency-version: 1.0.224
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: serde_derive
  dependency-version: 1.0.224
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: serde_json
  dependency-version: 1.0.145
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: tempfile
  dependency-version: 3.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-15 19:32:27 -04:00
dependabot[bot] b6f45b0a2e build(deps): bump the npm group across 1 directory with 4 updates
Bumps the npm group with 4 updates in the /lib/binding_web directory: [@types/emscripten](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/emscripten), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [typescript](https://github.com/microsoft/TypeScript) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `@types/emscripten` from 1.41.1 to 1.41.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/emscripten)

Updates `@types/node` from 24.3.1 to 24.5.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `typescript` from 5.8.3 to 5.9.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.8.3...v5.9.2)

Updates `typescript-eslint` from 8.43.0 to 8.44.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.44.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@types/emscripten"
  dependency-version: 1.41.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@types/node"
  dependency-version: 24.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: typescript
  dependency-version: 5.9.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: typescript-eslint
  dependency-version: 8.44.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-15 19:32:20 -04:00
ObserverOfTime 3d26b8e500 feat(bindings): use CapsuleType in Python stub 2025-09-14 19:00:25 -04:00
Amaan Qureshi 46f7f860e6 docs: correct explanation about @ignore capture 2025-09-14 16:45:39 -04:00
Amaan Qureshi 2ae677162f docs: clarify that only the whitespace character class is simplfied 2025-09-14 16:45:39 -04:00
Amaan Qureshi 4dbfb5b49a docs: document the @ignore capture in tags 2025-09-14 06:27:27 -04:00
Amaan Qureshi 3a911d578c docs: add more information on supertype nodes for grammars and queries 2025-09-14 06:25:35 -04:00
Amaan Qureshi 63f48afaeb docs: explain extras in a bit more detail 2025-09-14 06:02:00 -04:00
Will Lillis ac39aed7c5 fix(lib/wasm): return NULL for 0-sized allocations
Co-authored-by: Amaan Qureshi <contact@amaanq.com>
2025-09-13 22:29:47 -04:00
Amaan Qureshi c4d02a5254 build(lib): regenerate wasm stdlib
Co-authored-by: Will Lillis <will.lillis24@gmail.com>
2025-09-13 22:29:47 -04:00
Amaan Qureshi 69c42450c3 fix(lib/wasm): keep track of freed blocks that are not the last allocated pointer
This fixes issues where the scanner allocates and frees a lot of data
during a single parse.

Co-authored-by: Will Lillis <will.lillis24@gmail.com>
2025-09-13 22:29:47 -04:00
Amaan Qureshi b863b16454 fix(xtask): make building the wasm stdlib work again
Co-authored-by: Will Lillis <will.lillis24@gmail.com>
2025-09-13 22:29:47 -04:00
Will Lillis 0c35511aea fix(lib): improve wasm scanner serialization error handling
Co-authored-by: Amaan Qureshi <contact@amaanq.com>
2025-09-13 22:29:47 -04:00
Amaan Qureshi 06741d0d5d fix(playground): check that languageVersion exists for compat 2025-09-13 22:12:11 -04:00
RedCMD ca8b944b53
build(web): fix cjs build 2025-09-13 05:51:42 -04:00
Amaan Qureshi 580cd9541a feat(web)!: remove deprecated functions 2025-09-12 02:50:30 -04:00
Amaan Qureshi d60ef9ad0a feat(rust)!: remove deprecated functions 2025-09-12 02:50:30 -04:00
Amaan Qureshi 821cf797f2 feat(lib)!: remove deprecated functions 2025-09-12 02:50:30 -04:00
Amaan Qureshi 917895e6a3 build(nix): build cli with all features 2025-09-11 19:33:52 -04:00
Antonin Delpeuch 6e53dcc8e1
fix(playground): add missing <html> tag 2025-09-11 17:06:34 -04:00
ObserverOfTime 56325d2a3b chore: copy license to all packages 2025-09-11 03:12:35 -04:00
kilo52 d1160cb820 build(cmake)!: move CMakeLists.txt to the source root 2025-09-11 01:59:37 -04:00
kilo52 5528cfee17 build(cmake): change include directory scope to public
When a project adds tree-sitter as a dependency via the CMake
FetchContent machinery, the project fails to build because the
tree-sitter header cannot be found. This is because the include
directory is specified with the private scope instead of public.
2025-09-11 01:59:37 -04:00
Daniel Müller 937dcf5fd1 feat(rust)!: use ops::ControlFlow as parse and query progress return value
Instead of returning an undocumented boolean flag, use a
core::ops::ControlFlow object. At the expense of being a bit more
verbose, this is a type that should be self-explanatory in the context
of a callback, as an indication of whether to continue processing or
stop.
2025-09-11 01:59:21 -04:00
dependabot[bot] 57e2f41f42 build(deps): bump vite from 7.1.3 to 7.1.5 in /lib/binding_web
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.3 to 7.1.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.5/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-10 23:27:36 -04:00
Amaan Qureshi 3c1f02a7f9 docs(playground): show toast when tree is copied 2025-09-09 03:13:35 -04:00
Amaan Qureshi 907c7bc80b docs(playground): add version information in playground
Co-authored-by: Will Lillis <will.lillis24@gmail.com>
2025-09-09 03:13:35 -04:00
Amaan Qureshi 40a8678989 refactor(xtask): don't use docker to run wasi-sdk's clang
This now relies on TREE_SITTER_WASI_SDK_PATH being set
2025-09-09 03:13:35 -04:00
Amaan Qureshi 6451d2f65d fix(web): correct metadata function 2025-09-09 03:13:35 -04:00
faukah e4e643086b
refactor: rework nix flake
Co-authored-by: Amaan Qureshi <amaanq12@gmail.com>
2025-09-09 03:03:35 -04:00
Amaan Qureshi e9e4316569 feat(xtask): update nix 2025-09-09 04:45:57 +02:00
Amaan Qureshi 0719bd6ffb feat(xtask): bump versions in package-lock.json 2025-09-09 04:45:57 +02:00
Amaan Qureshi 8d6d19de1b build: add package-lock.json for npm cli package 2025-09-09 04:45:57 +02:00
dependabot[bot] 16502e19dd build(deps): bump the npm group across 1 directory with 6 updates
Bumps the npm group with 6 updates in the /lib/binding_web directory:

| Package | From | To |
| --- | --- | --- |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.34.0` | `9.35.0` |
| [@types/emscripten](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/emscripten) | `1.40.0` | `1.41.1` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `24.3.0` | `24.3.1` |
| [eslint](https://github.com/eslint/eslint) | `9.34.0` | `9.35.0` |
| [typescript](https://github.com/microsoft/TypeScript) | `5.8.3` | `5.9.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.41.0` | `8.43.0` |



Updates `@eslint/js` from 9.34.0 to 9.35.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.35.0/packages/js)

Updates `@types/emscripten` from 1.40.0 to 1.41.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/emscripten)

Updates `@types/node` from 24.3.0 to 24.3.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `eslint` from 9.34.0 to 9.35.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.34.0...v9.35.0)

Updates `typescript` from 5.8.3 to 5.9.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.8.3...v5.9.2)

Updates `typescript-eslint` from 8.41.0 to 8.43.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.43.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.35.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: "@types/emscripten"
  dependency-version: 1.41.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: "@types/node"
  dependency-version: 24.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: eslint
  dependency-version: 9.35.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: typescript
  dependency-version: 5.9.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: typescript-eslint
  dependency-version: 8.43.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-09 04:33:49 +02:00
dependabot[bot] 6b2ed39df6 ci: bump the actions group with 2 updates
Bumps the actions group with 2 updates: [actions/setup-node](https://github.com/actions/setup-node) and [actions/github-script](https://github.com/actions/github-script).


Updates `actions/setup-node` from 4 to 5
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v5)

Updates `actions/github-script` from 7 to 8
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/github-script
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-09 04:33:22 +02:00
dependabot[bot] d517af4c1a build(deps): bump the cargo group with 3 updates
Bumps the cargo group with 3 updates: [cc](https://github.com/rust-lang/cc-rs), [ctrlc](https://github.com/Detegr/rust-ctrlc) and [log](https://github.com/rust-lang/log).


Updates `cc` from 1.2.35 to 1.2.36
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.35...cc-v1.2.36)

Updates `ctrlc` from 3.4.7 to 3.5.0
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/commits)

Updates `log` from 0.4.27 to 0.4.28
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.27...0.4.28)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.36
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: ctrlc
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
- dependency-name: log
  dependency-version: 0.4.28
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-09 04:33:02 +02:00
Shadaj Laddad 27e5147a5f feat(generate): place file APIs behind a feature flag 2025-09-07 11:44:09 +03:00
Amaan Qureshi e659dddad1 build(nix): add cross and typescript to dev shell 2025-09-04 05:00:14 -04:00
Amaan Qureshi 643f532a70 chore: update flake 2025-09-04 05:00:14 -04:00
Amaan Qureshi 86d86628cb build(nix): use fenix for rust toolchain
This plays more nicely with cross for local testing
2025-09-04 05:00:14 -04:00
Amaan Qureshi 17854168d9 feat(nix): add cross compilation support 2025-09-04 05:00:14 -04:00
Amaan Qureshi d287acfcc0 refactor(nix): remove openssl dependency 2025-09-04 05:00:14 -04:00
Amaan Qureshi 595299a3c2 ci: remove separate step for generate fixtures 2025-09-04 04:22:32 -04:00
Amaan Qureshi 3eabba637c fix(xtask): run all tests with the --all flag 2025-09-04 04:22:32 -04:00
Amaan Qureshi cd12e66e67 fix(lib): don't run code snippet as a doctest 2025-09-04 04:22:32 -04:00
Amaan Qureshi 1d0ebd1065 fix(rust): do not interpret readme doc comments as doc tests 2025-09-04 04:22:32 -04:00
Will Lillis 5fd818babe fix(generate): use correct state id when adding terminal states to
non terminal extras
2025-09-04 04:15:51 -04:00
Amaan Qureshi 968d39328d ci: switch to arm runner 2025-09-04 01:47:26 -04:00
Will Lillis 310c0b86a7 fix(generate): return error when single state transitions have
indirectly recursive cycles.

This can cause infinite loops in the parser near EOF.

Co-authored-by: Amaan Qureshi <amaanq12@gmail.com>
2025-09-04 00:59:44 -04:00
Will Lillis cc5463ad44 fix(test): improve readability of corpus error message mismatch 2025-09-04 00:59:44 -04:00
Amaan Qureshi f95a52df4a fix(xtask): reset fixtures when checking out new version 2025-09-04 00:59:01 -04:00
Amaan Qureshi 3b0273fc61 build: bump emscripten to 4.0.12 2025-09-04 00:59:01 -04:00
Amaan Qureshi 6cb9486b28 test(fixtures): bump javascript grammar to 0.25.0 2025-09-04 00:59:01 -04:00
Amaan Qureshi f44cbd407f perf(xtask); check out the tag directly for fixtures 2025-09-03 02:43:32 -04:00
Amaan Qureshi 0b836b2de0 ci: use armv7hf 2025-09-02 16:07:37 -04:00
Amaan Qureshi 151130a5df ci: pass in matrix target to xtask commands 2025-09-02 16:07:37 -04:00
Amaan Qureshi 4559ec51f7 fix(loader): correct logic when downloading wasi-sdk 2025-09-02 16:07:37 -04:00
Amaan Qureshi 5cd6e747a0 refactor(xtask): use the git binary to perform git operations 2025-09-02 16:07:37 -04:00
Amaan Qureshi 5263cd0706 refactor(loader): use the tar binary to extract the wasi sdk 2025-09-02 16:07:37 -04:00
Amaan Qureshi 4535ea6aaa refactor(loader): use the curl binary instead of ureq to download wasi-sdk 2025-09-02 16:07:37 -04:00
ObserverOfTime 6e8ad7e5cc fix(xtask): update paths in bump-version
and remove the toml dependency
2025-09-02 14:22:01 -04:00
Amaan Qureshi 22d658518b feat(loader): allow specifying the wasi sdk path 2025-09-01 23:14:20 -04:00
Amaan Qureshi f0cfaffd5e build(web): mark @types/emscripten as a dev dep 2025-09-01 23:14:20 -04:00
Amaan Qureshi 94996b26e5 refactor(web): clean up preserving sourcemap logic, don't copy lib files 2025-09-01 23:14:20 -04:00
Amaan Qureshi f2e71ec95c fix(xtask): restore stripped sourcesContent when building the wasm module 2025-09-01 23:14:20 -04:00
Amaan Qureshi da61d7cac5 feat: add nix flake 2025-09-01 22:48:37 -04:00
Boris Verkhovskiy 9b23cd5394
feat: use compileStreaming when loading wasm parsers 2025-09-01 22:08:20 -04:00
ObserverOfTime b75196bb81 feat(c): rename DecodeFunction to TSDecodeFunction
Keep a typedef for backwards compatibility until ABI 16.
2025-09-01 03:17:44 -04:00
ObserverOfTime dbe88f8bbb fixup: remove permission check 2025-09-01 07:35:40 +03:00
ObserverOfTime f9a331a505 ci(spam): fix permission check 2025-09-01 07:35:40 +03:00
ObserverOfTime 47beafb836 fix(playground): account for missing button 2025-08-31 14:08:06 -04:00
Will Lillis a12a79b366 fix(cli): canonicalize --lib-path arguments
This fixes an issue where "./foo.so" would would work but "foo.so" would not.
2025-08-31 01:10:43 -04:00
Will Lillis 62f93e221d feat(test): pin fixture grammars to specific commits 2025-08-31 01:05:42 -04:00
ObserverOfTime 07986471b3 feat(xtask): automate edits to emscripten generated d.ts 2025-08-30 18:14:42 -04:00
ObserverOfTime dba8446d9e feat(web)!: use bigint for timeouts 2025-08-30 18:14:42 -04:00
ObserverOfTime 0c5cdcb161 fix(xtask): add heap symbols to emscripten exported methods 2025-08-30 18:14:42 -04:00
ObserverOfTime 8b3e023ef0 chore(web): remove source files from package
and generated files from version control
2025-08-30 18:14:42 -04:00
dependabot[bot] a0fd1ded97 build(deps): bump wasmparser from 0.224.1 to 0.229.0 in the cargo group
Bumps the cargo group with 1 update: [wasmparser](https://github.com/bytecodealliance/wasm-tools).


Updates `wasmparser` from 0.224.1 to 0.229.0
- [Release notes](https://github.com/bytecodealliance/wasm-tools/releases)
- [Commits](https://github.com/bytecodealliance/wasm-tools/commits)

---
updated-dependencies:
- dependency-name: wasmparser
  dependency-version: 0.229.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-30 18:13:55 -04:00
ObserverOfTime 5272b6b908 refactor(web): use nullish coalescing operator 2025-08-30 18:13:39 -04:00
dependabot[bot] a866eb5dd0 build(deps): bump the npm group across 1 directory with 9 updates
Bumps the npm group with 8 updates in the /lib/binding_web directory:

| Package | From | To |
| --- | --- | --- |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.13.1` | `24.3.0` |
| [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) | `3.0.5` | `3.2.4` |
| [dts-buddy](https://github.com/Rich-Harris/dts-buddy) | `0.5.4` | `0.6.2` |
| [esbuild](https://github.com/evanw/esbuild) | `0.25.0` | `0.25.9` |
| [source-map](https://github.com/mozilla/source-map) | `0.7.4` | `0.7.6` |
| [tsx](https://github.com/privatenumber/tsx) | `4.20.3` | `4.20.5` |
| [typescript](https://github.com/microsoft/TypeScript) | `5.7.3` | `5.9.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.23.0` | `8.41.0` |



Updates `@types/node` from 22.13.1 to 24.3.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@vitest/coverage-v8` from 3.0.5 to 3.2.4
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v3.2.4/packages/coverage-v8)

Updates `dts-buddy` from 0.5.4 to 0.6.2
- [Release notes](https://github.com/Rich-Harris/dts-buddy/releases)
- [Changelog](https://github.com/sveltejs/dts-buddy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Rich-Harris/dts-buddy/compare/v0.5.4...v0.6.2)

Updates `esbuild` from 0.25.0 to 0.25.9
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.0...v0.25.9)

Updates `source-map` from 0.7.4 to 0.7.6
- [Release notes](https://github.com/mozilla/source-map/releases)
- [Changelog](https://github.com/mozilla/source-map/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mozilla/source-map/compare/v0.7.4...0.7.6)

Updates `tsx` from 4.20.3 to 4.20.5
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.20.3...v4.20.5)

Updates `typescript` from 5.7.3 to 5.9.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.7.3...v5.9.2)

Updates `typescript-eslint` from 8.23.0 to 8.41.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.41.0/packages/typescript-eslint)

Updates `vitest` from 3.0.5 to 3.2.4
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v3.2.4/packages/vitest)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.3.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm
- dependency-name: "@vitest/coverage-v8"
  dependency-version: 3.2.4
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: dts-buddy
  dependency-version: 0.6.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: esbuild
  dependency-version: 0.25.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: source-map
  dependency-version: 0.7.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: tsx
  dependency-version: 4.20.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: typescript
  dependency-version: 5.9.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: typescript-eslint
  dependency-version: 8.41.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: vitest
  dependency-version: 3.2.4
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-30 18:13:39 -04:00
ObserverOfTime 90bdd63a71 fix(npm): add directory to repository fields
and remove non-existent "main" entry point
2025-08-30 17:16:25 -04:00
ObserverOfTime ed2abf8609 ci(dependabot): update npm dependencies 2025-08-30 17:08:46 -04:00
ObserverOfTime d188bf6352 chore: remove emscripten xtask & workflow 2025-08-30 16:52:22 -04:00
vemoo 4db3edadf4
fix(web): correct type errors, improve build 2025-08-30 16:51:41 -04:00
ObserverOfTime ca27fb5d43 fix(cli): fix DSL type declarations 2025-08-30 16:47:47 -04:00
ObserverOfTime e6fe91e2e7 build(zig): support wasmtime in more archs 2025-08-30 16:47:47 +03:00
ObserverOfTime b57b7213a9 fix(rust): make some methods const
Addresses clippy::missing-const-for-fn
2025-08-30 16:47:47 +03:00
ObserverOfTime ed91767663 build(rust)!: bump MSRV to 1.84.0
Required by wasmtime
2025-08-30 16:47:47 +03:00
ObserverOfTime 489ad07e8b build(deps): bump wasmtime-c-api to v33.0.2 2025-08-30 16:47:47 +03:00
ObserverOfTime 88e323ca43 fix(xtask): commit Cargo.lock in upgrade_wasmtime
And add missing zig dependency update
2025-08-30 16:47:47 +03:00
RedCMD 721b0e8b11
fix(web): update .d.ts files 2025-08-29 21:39:53 -04:00
Will Lillis ac171eb280 fix(generate): warn users when extra rule can lead to parser hang
When a *named* rule in the extras is able to match the empty string,
parsing can hang in certain situations (i.e. near EOF).
2025-08-29 21:38:41 -04:00
Boris Verkhovskiy a1211d3fbd
feat(web): inline C source code in source map 2025-08-29 14:26:27 -04:00
ObserverOfTime 0be215e152 fix(bindings): properly detect MSVC compiler 2025-08-29 13:32:55 +03:00
ObserverOfTime c12b4a1565 ci: add a spam closing workflow 2025-08-29 13:26:53 +03:00
WillLillis 34ef1157a6 feat(ci): build libraries on windows x64 with mingw 2025-08-28 21:28:48 -04:00
ObserverOfTime e0edfe1cb3 build(zig): support wasmtime for ARM64 Windows (MSVC) 2025-08-29 01:12:46 +03:00
Amaan Qureshi 8387101a61 fix(lib): allow error nodes to match when they are child nodes 2025-08-28 18:02:47 -04:00
dependabot[bot] 9fdf7213d4 ci: bump actions/upload-pages-artifact from 3 to 4 in the actions group
Bumps the actions group with 1 update: [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact).


Updates `actions/upload-pages-artifact` from 3 to 4
- [Release notes](https://github.com/actions/upload-pages-artifact/releases)
- [Commits](https://github.com/actions/upload-pages-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-pages-artifact
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-28 17:15:24 -04:00
Amaan Qureshi b7f36a13ba fix(lib): check if an ERROR node is named before assuming it's the builtin error node 2025-08-28 17:13:57 -04:00
ObserverOfTime 66ea1a6dda style(zig): reformat files 2025-08-28 20:54:25 +03:00
ObserverOfTime 298b6775c6 build(zig): use ArrayListUnmanaged
This is supported in 0.14 and 0.15
2025-08-28 20:54:25 +03:00
ObserverOfTime 2e4b7d26b1 build(zig): don't link wasmtime in static build 2025-08-28 20:54:25 +03:00
ObserverOfTime dab84a1b10 build(zig): expose wasmtimeDep function
This allows consumers to reuse the dependency.
2025-08-28 20:54:25 +03:00
Will Lillis 340df02655
docs: clarify definition of newline 2025-08-28 13:31:25 -04:00
Amaan Qureshi 99d8b58868 docs: clarify that a reserved rule must exist in the grammar 2025-08-28 13:28:37 -04:00
Amaan Qureshi 1152bf4c9d fix(cli): show rule names for rules with metadata 2025-08-28 13:28:37 -04:00
ObserverOfTime 107bd800b0 fix(bindings): improve zig dependency fetching logic
Currently, including a tree-sitter parser as a dependency in a zig
project and running `zig build test` on the project will fetch the
zig-tree-sitter dependency declared by the parser. This is a problem
because (a) consumers may not want this dependency for whatever reason
and (b) due to how often Zig breaks everything and how scarcely most
tree-sitter parsers are updated, the zig-tree-sitter version pinned
by the parser module will often be outdated and broken.

The workaround I used was taken from https://ziggit.dev/t/11234
2025-08-28 10:31:24 +03:00
ObserverOfTime e67f9f8f7a fix(bindings): add tree-sitter as npm dev dependency
npm is supposed to automatically install peer dependencies since v7
but sometimes it's not doing it and we need this dependency for tests
2025-08-27 19:34:09 +03:00
Quentin LE DILAVREC 79177a1cd5
fix(rust): EqCapture accepted cases where number of captured nodes differed by one
Problem: When using alternations, the `#eq?` predicate does not always use the same capture name.

Solution: Iterate the left and right captured nodes more independently.
2025-08-27 10:25:29 +02:00
dependabot[bot] 0a7274678a build(deps): bump the cargo group with 8 updates
Bumps the cargo group with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.33` | `1.2.34` |
| [filetime](https://github.com/alexcrichton/filetime) | `0.2.25` | `0.2.26` |
| [indexmap](https://github.com/indexmap-rs/indexmap) | `2.10.0` | `2.11.0` |
| [regex](https://github.com/rust-lang/regex) | `1.11.1` | `1.11.2` |
| [regex-syntax](https://github.com/rust-lang/regex) | `0.8.5` | `0.8.6` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.142` | `1.0.143` |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.20.0` | `3.21.0` |
| [thiserror](https://github.com/dtolnay/thiserror) | `2.0.15` | `2.0.16` |


Updates `cc` from 1.2.33 to 1.2.34
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.33...cc-v1.2.34)

Updates `filetime` from 0.2.25 to 0.2.26
- [Commits](https://github.com/alexcrichton/filetime/commits)

Updates `indexmap` from 2.10.0 to 2.11.0
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.10.0...2.11.0)

Updates `regex` from 1.11.1 to 1.11.2
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.11.1...1.11.2)

Updates `regex-syntax` from 0.8.5 to 0.8.6
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/regex-syntax-0.8.5...regex-syntax-0.8.6)

Updates `serde_json` from 1.0.142 to 1.0.143
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.142...v1.0.143)

Updates `tempfile` from 3.20.0 to 3.21.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/commits)

Updates `thiserror` from 2.0.15 to 2.0.16
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.15...2.0.16)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.34
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: filetime
  dependency-version: 0.2.26
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: indexmap
  dependency-version: 2.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
- dependency-name: regex
  dependency-version: 1.11.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: regex-syntax
  dependency-version: 0.8.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: serde_json
  dependency-version: 1.0.143
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: tempfile
  dependency-version: 3.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
- dependency-name: thiserror
  dependency-version: 2.0.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 16:11:45 +02:00
WillLillis 0d914c860a fix(wasm): delete var_i32_type after initializing global stack
pointer value
2025-08-25 17:16:02 -04:00
ObserverOfTime eb5ad7eb26 feat(playground): add a button to copy the tree
Co-Authored-By: Firas al-Khalil <firasalkhalil@gmail.com>
2025-08-25 10:21:48 +03:00
ObserverOfTime 4602e60c1b refactor(playground): move style to head 2025-08-25 10:21:48 +03:00
ObserverOfTime 1c640ac2c3 fix(cli): correct resource paths 2025-08-25 10:21:48 +03:00
John-Philip Taylor 10f5a42fd7 feat(cli): Version updates (#4179)
- `version` on its own displays the current version
- `version --bump patch` bumps the patch version
- `version --bump minor` bumps the minor version
- `version --bump major` bumps the major version
- `version 1.2.3` bumps the version directly (existing behaviour)

All flavours of version bump displays a string in the form:
`Bumping version 1.2.3 to 4.5.6`
2025-08-24 00:53:41 -04:00
Will Lillis fee50ad0ce feat(lib): add lldb pretty-printers for some C lib types 2025-08-22 20:10:03 -04:00
Alexander von Gluck be888a5fef
fix(c): add Haiku support to endian.h 2025-08-22 14:09:13 +03:00
bfredl 86b9f81ad6 build(zig)!: upgrade build.zig to zig 0.15
This no longer supports zig 0.14.
2025-08-21 11:44:02 +02:00
ObserverOfTime 88e0b4cea4 docs: change WASM/wasm to Wasm
That is the official capitalisation.
2025-08-21 09:56:32 +03:00
dependabot[bot] 7bc8f76667 build(deps): bump the cargo group with 5 updates
Bumps the cargo group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.98` | `1.0.99` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.32` | `1.2.33` |
| [clap](https://github.com/clap-rs/clap) | `4.5.44` | `4.5.45` |
| [thiserror](https://github.com/dtolnay/thiserror) | `2.0.12` | `2.0.15` |
| [ureq](https://github.com/algesten/ureq) | `3.0.12` | `3.1.0` |


Updates `anyhow` from 1.0.98 to 1.0.99
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.98...1.0.99)

Updates `cc` from 1.2.32 to 1.2.33
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.32...cc-v1.2.33)

Updates `clap` from 4.5.44 to 4.5.45
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.44...clap_complete-v4.5.45)

Updates `thiserror` from 2.0.12 to 2.0.15
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.12...2.0.15)

Updates `ureq` from 3.0.12 to 3.1.0
- [Changelog](https://github.com/algesten/ureq/blob/main/CHANGELOG.md)
- [Commits](https://github.com/algesten/ureq/compare/3.0.12...3.1.0)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.99
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: cc
  dependency-version: 1.2.33
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-version: 4.5.45
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: thiserror
  dependency-version: 2.0.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: ureq
  dependency-version: 3.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 10:26:40 +02:00
ObserverOfTime 0bb43f7afb fix(playground): scroll with vanilla JS
jQuery must burn in fire.
2025-08-14 11:03:52 +03:00
Soheil Salmani 99c121bfe8
docs: replace TSInput by TSInputEdit in "Advanced Parsing" 2025-08-13 22:58:17 +03:00
Carlos Scheidegger c60264b87f
docs(web): fix deno import example 2025-08-13 22:56:21 +03:00
Andrey L. f9f7ae0850
fix(bindings): make npm repository field an object 2025-08-13 22:54:37 +03:00
ObserverOfTime d45642c345 fix(bindings): don't ignore lockfiles 2025-08-13 22:51:12 +03:00
dependabot[bot] 790dcc115f ci: bump the actions group with 2 updates
Bumps the actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [actions/download-artifact](https://github.com/actions/download-artifact).


Updates `actions/checkout` from 4 to 5
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

Updates `actions/download-artifact` from 4 to 5
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/download-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-13 12:35:25 +02:00
dependabot[bot] 79a5e6a671 build(deps): bump the cargo group with 4 updates
Bumps the cargo group with 4 updates: [cc](https://github.com/rust-lang/cc-rs), [clap](https://github.com/clap-rs/clap), [clap_complete](https://github.com/clap-rs/clap) and [glob](https://github.com/rust-lang/glob).


Updates `cc` from 1.2.31 to 1.2.32
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.31...cc-v1.2.32)

Updates `clap` from 4.5.42 to 4.5.44
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.42...clap_complete-v4.5.44)

Updates `clap_complete` from 4.5.55 to 4.5.57
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.55...clap_complete-v4.5.57)

Updates `glob` from 0.3.2 to 0.3.3
- [Release notes](https://github.com/rust-lang/glob/releases)
- [Changelog](https://github.com/rust-lang/glob/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/glob/compare/v0.3.2...v0.3.3)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-version: 4.5.44
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete
  dependency-version: 4.5.57
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: glob
  dependency-version: 0.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 10:26:29 +02:00
Will Lillis 3a47bc4435 fix(cli): escape delimiters '"' and '`' in cst node text 2025-08-11 22:39:12 -04:00
Will Lillis 3294b04436 fix(cli): remove trailing whitespace in cst print 2025-08-10 14:52:25 -04:00
Will Lillis 2c02f0767b fix(docs): correct awkward wording in fail-fast listing 2025-08-10 14:52:25 -04:00
Will Lillis 6f7f9a3869 refactor(test): replace long tuples with structs 2025-08-10 14:52:25 -04:00
Will Lillis 1704c604bf feat(test): allow cst as expected output of test case 2025-08-10 14:52:25 -04:00
Will Lillis 21b38004da fix(rust): adapt to new nightly iterator lints 2025-08-10 13:28:52 -04:00
Will Lillis aed8b8b32c feat(cli): rebuild parsers by default when --grammar-path is passed. 2025-08-10 13:28:52 -04:00
Will Lillis 1562b719d2 feat(cli): add --rebuild flag to query, highlight, and tags commands 2025-08-10 13:28:52 -04:00
Will Lillis e749a97f9f fix(rust): make String clones more explicit 2025-08-09 16:02:28 -04:00
Will Lillis 3d9f4d1bd3 fix(rust): remove redundant String clones 2025-08-09 16:02:28 -04:00
ObserverOfTime 9f2dd2f377 fix(bindings): fix clean makefile target 2025-08-09 14:41:22 +03:00
Ronald T. Casili d87921bb9c
fix(bindings): update zig template files (#4637) 2025-08-09 10:18:07 +03:00
ObserverOfTime f7838748df fix(cli): drop homepage link from config
We don't actually use this currently and the vast majority of grammars
don't have a homepage other than the repository itself.
2025-08-09 10:14:17 +03:00
ObserverOfTime 7e7e376046 refactor(cli): conditional assignment consistency 2025-08-08 10:53:57 +03:00
ObserverOfTime bbec252c51 refactor(cli): drop unused function 2025-08-08 10:53:57 +03:00
ObserverOfTime 9d619d6fdc fix(bindings): use custom class name 2025-08-08 10:53:57 +03:00
Will Lillis 49ae48f7fe fix(ci): ignore mismatched_lifetime_syntaxes lint when building wasmtime 2025-08-08 09:44:09 +02:00
Will Lillis 46a0e94de7 fix(rust): ignore new mismatched-lifetime-syntaxes lint 2025-08-08 09:44:09 +02:00
dependabot[bot] 2368af52ea build(deps): bump the cargo group with 5 updates
Bumps the cargo group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.30` | `1.2.31` |
| [clap](https://github.com/clap-rs/clap) | `4.5.41` | `4.5.42` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.141` | `1.0.142` |
| [notify](https://github.com/notify-rs/notify) | `8.1.0` | `8.2.0` |
| [notify-debouncer-full](https://github.com/notify-rs/notify) | `0.5.0` | `0.6.0` |


Updates `cc` from 1.2.30 to 1.2.31
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.30...cc-v1.2.31)

Updates `clap` from 4.5.41 to 4.5.42
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.41...clap_complete-v4.5.42)

Updates `serde_json` from 1.0.141 to 1.0.142
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.141...v1.0.142)

Updates `notify` from 8.1.0 to 8.2.0
- [Release notes](https://github.com/notify-rs/notify/releases)
- [Changelog](https://github.com/notify-rs/notify/blob/main/CHANGELOG.md)
- [Commits](https://github.com/notify-rs/notify/compare/notify-8.1.0...notify-8.2.0)

Updates `notify-debouncer-full` from 0.5.0 to 0.6.0
- [Release notes](https://github.com/notify-rs/notify/releases)
- [Changelog](https://github.com/notify-rs/notify/blob/main/CHANGELOG.md)
- [Commits](https://github.com/notify-rs/notify/compare/debouncer-full-0.5.0...debouncer-full-0.6.0)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.31
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-version: 4.5.42
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: serde_json
  dependency-version: 1.0.142
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: notify
  dependency-version: 8.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
- dependency-name: notify-debouncer-full
  dependency-version: 0.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-05 09:27:17 +02:00
Will Lillis d000698847 fix(docs): add information on missing cli command options 2025-08-03 14:34:04 -04:00
Will Lillis d641249f85 feat(cli): include language name in dump-languages output 2025-08-03 14:34:04 -04:00
Will Lillis 346080aad2 refactor(loader): replace replace_dashes_with_underscores with rust std lib 2025-08-03 14:34:04 -04:00
Will Lillis acfeed006a feat(cli): allow users to specify dynamic libraries directly for
parse, query, test, and fuzz subcommands
2025-08-03 14:34:04 -04:00
Will Lillis 5e1daf0c41 fix(rust)!: take u32 for index parameter to Node::{child, named_child}
Co-authored-by: Ye Sijun <junnplus@gmail.com>
2025-08-03 12:58:53 -04:00
Will Lillis 7d0b94c008 fix(docs): indicate CJS env var export is different on Windows platforms 2025-08-03 12:58:34 -04:00
Will Lillis d3c2fed4b3 fix(rust): correct indices for Node::utf16_text 2025-08-02 16:03:55 -04:00
dependabot[bot] 32c88194f5 build(deps): bump the cargo group with 2 updates
Bumps the cargo group with 2 updates: [cc](https://github.com/rust-lang/cc-rs) and [serde_json](https://github.com/serde-rs/json).


Updates `cc` from 1.2.29 to 1.2.30
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.29...cc-v1.2.30)

Updates `serde_json` from 1.0.140 to 1.0.141
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.140...v1.0.141)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.30
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: serde_json
  dependency-version: 1.0.141
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-02 11:59:23 +02:00
Will Lillis 9ced6172de
fix(cli): improve error message when language in list can't be found (#4643)
Problem: When multiple input paths are provided to the `parse` command (a la `tree-sitter parse --paths [...]`), if a language can't be found for one of the paths, it can be a little unclear *which* path caused the failure. The loader *can* fail with `Failed to load language for file name <foo.bar>`, but this isn't guaranteed. 

Solution: Attach some additional context in the case where multiple paths can be provided, displaying the problematic path on failure.
2025-08-02 11:52:54 +02:00
Pieter Goetschalckx 07b4c8d05d
fix(cli): add reserved type declarations and schema
- Use `globalThis` for `reserved` function export
- Add `reserved` field and function to DSL declarations
- Add `reserved` rule to grammar schema
2025-08-02 12:01:41 +03:00
Antonin Delpeuch b787f31481
feat(bindings): generate in 2 steps via make/cmake (#4646)
This makes it possible to run `make` or `cmake --build` in grammar
repos where `grammar.json` hasn't been included, and still get the parser
compilation to work.
2025-08-02 11:57:29 +03:00
Riley Bruins 6850df969d fix(query): prevent cycles when analyzing hidden children
**Problem:** `query.c` compares the current analysis state with the
previous analysis state to see if they are equal, so that it can return
early if so. This prevents redundant work. However, the comparison
function here differs from the one used for sorted insertion/lookup in
that it does not check any state data other than the child index. This
is problematic because it leads to infinite analysis when hidden nodes
have cycles.

**Solution:** Remove the custom comparison function, and apply the
insertion/lookup comparison function in place of it.

**NOTE:** This commit also changes the comparison function slightly, so
that some comparisons are reordered. Namely, for performance, it returns
early if the lhs depth is less than the rhs depth. Is this acceptable?
Tests still pass and nothing hangs in my testing, but it still seems
sketchy. Returning early if the lhs depth is greater than the rhs depth
does seem to make query analysis hang, weirdly enough... Keeping the
depth checks at the end of the loop also works, but it introduces a
noticeable performance regression (for queries that otherwise wouldn't
have had analysis cycles, of course).
2025-07-30 00:41:01 -04:00
Will Lillis 62c53197c4 fix(cli): eliminate leak of version string 2025-07-29 20:44:48 -04:00
Will Lillis cd2cb661a4 fix(docs): update cli path for install commands 2025-07-29 02:42:43 -04:00
Antonin Delpeuch 8e90799e27 feat(generate): Support for --stage=json/parser/lib 2025-07-26 22:48:00 -04:00
Antonin Delpeuch d810217e63 refactor: Expose node_types_json without generating C code 2025-07-26 22:48:00 -04:00
Antonin Delpeuch 8676eda663 feat: Only evaluate grammar.js to grammar.json
This adds an `--evaluate-only` option to `tree-sitter generate`
so that it only does the evaluation of `grammar.js` to
`src/grammar.json`, without continuing on with the generation of
`src/parser.c` and related files.

It's a follow-up to #4580.
2025-07-26 22:48:00 -04:00
Robert Muir 8c61bbdb73 feat(bindings): improve python binding test
Previously, the test would not detect ABI incompatibilities.
2025-07-25 13:22:49 +03:00
ObserverOfTime 99988b7081 fix(bindings): improve python platform detection 2025-07-25 13:21:36 +03:00
ObserverOfTime 436162ae7c fix(bindings): only include top level LICENSE file
Ref: tree-sitter/workflows#33
2025-07-25 13:21:14 +03:00
ObserverOfTime c3012a7d8a fix(bindings): use parser title in lib.rs description 2025-07-25 13:19:03 +03:00
Daniel Müller f0165dee92 feat(rust): Add direct links to documentation
Make the URL to the documentation of the various crates known so that it
will be easier to click through to it from a crates.io search [0].

[0] https://doc.rust-lang.org/cargo/reference/manifest.html#the-documentation-field
2025-07-22 18:36:08 -04:00
Riley Bruins dff828cdbe fix(rust): prevent overflow in error message calculation
**Problem:** When encountering an invalid symbol at the beginning of the
file, the rust bindings attempt to index the character at position -1 of
the query source, which leads to an overflow and thus invalid character
index which causes a panic.

**Solution:** Bounds check the offset before performing the subtraction.
2025-07-22 09:51:28 +02:00
Christian Clason c18d019db0 Revert "feat(generate): warn about unreachable variables (#4567)"
This reverts commit 25c601bd2f.

Reason: Too many false positives.
2025-07-22 09:49:26 +02:00
Antonin Delpeuch 25c601bd2f
feat(generate): warn about unreachable variables (#4567) 2025-07-21 11:39:18 +02:00
Will Lillis 85ab2929e9 docs(license): update year 2025-07-20 21:14:47 -04:00
Will Lillis 86b2c939c7 fix(rust): address nightly clippy lint useless_if_let_seq 2025-07-20 21:14:47 -04:00
Benjamin A. Beasley e3b85fd0d5 feat(cargo): update etcetera to 0.10
allows dropping a number of windows-targets crates
2025-07-20 12:47:17 +02:00
Will Lillis 0f79c61188 perf(generate): reserve more Vec capacities 2025-07-17 01:51:56 -04:00
Ronald T. Casili 618b9dd66e
fix(build.zig): remove deprecated addStaticLibrary() 2025-07-16 11:08:07 +02:00
dependabot[bot] ccc707152a build(deps): bump the cargo group with 3 updates
---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.41
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete
  dependency-version: 4.5.55
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete_nushell
  dependency-version: 4.5.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-15 07:17:05 +00:00
Alex Aron aeab755033
fix(lib): add wasm32 support to portable/endian.h (#4607) 2025-07-14 17:47:40 +02:00
RedCMD 912167a9cd
chore(web): fix indentation in type files 2025-07-14 12:50:04 +02:00
ObserverOfTime 42e2bba8d6 build(bindings): update Makefile
Similar to the changes made to the core Makefile.
2025-07-14 11:02:19 +03:00
Antonin Décimo 56c2fe59cb build(make): cleanup include dir on uninstall 2025-07-14 11:02:19 +03:00
Antonin Décimo c7f877de96 build(make): invoke ln in the link directory 2025-07-14 11:02:19 +03:00
Antonin Décimo aa11c198af ci: don't build the C library with MinGW
Only the C compiler is currently installed, without its binutils.
2025-07-14 11:02:19 +03:00
Antonin Décimo f8f93c1ec1 build(make): fix MinGW builds
Rename libtree-sitter.lib target to libtree-sitter.dll.a which is
what MinGW actually uses, move the target after libtree-sitter.dll,
and make sure it is installed to the correct location.

Also avoid invoking `cc -dumpmachine` multiple times.
2025-07-14 11:02:19 +03:00
ObserverOfTime f04fab9b7a docs: update bindings list 2025-07-14 10:24:17 +03:00
Will Lillis 854f527f6e perf: reorder conditional operands 2025-07-13 19:42:30 +02:00
Will Lillis c740f244ba fix(generate): propagate node types error 2025-07-13 19:42:30 +02:00
Will Lillis 5ed2c77b59 perf: reorder expensive conditional operand 2025-07-12 13:51:54 -04:00
Will Lillis ad0f953c21 chore(rust): remove empty grammar_files file 2025-07-10 23:44:10 -04:00
Will Lillis 3c56f53105 fix(test): unwrap result of get_node_types in
`test_node_types_deeper_extras` test
2025-07-10 17:42:52 -04:00
WillLillis d2e06bf130 fix(generate): use topological sort for subtype map 2025-07-10 08:57:55 -04:00
Antonin Delpeuch 36d93aeff3 perf: More efficient computation of used symbols
As the call to `symbol_is_used` does not depend
on the production, it is more efficient to call it
only once outside the loop over productions.

I'm not sure if `rustc` is able to do this optimization
on its own (it would need to know that the function
is pure, which sounds difficult in general).
2025-07-09 22:34:53 -04:00
Will Lillis 1e7d77c517 perf: reserve Vec capacities where appropriate 2025-07-09 21:46:25 -04:00
liangmulu 81849352fc chore: remove redundant words in comment
Signed-off-by: liangmulu <liangmulu@outlook.com>
2025-07-09 18:38:37 +02:00
dependabot[bot] 01d8a39c0b build(deps): bump the cargo group across 1 directory with 3 updates
Bumps the cargo group with 3 updates in the / directory: [cc](https://github.com/rust-lang/cc-rs), [indexmap](https://github.com/indexmap-rs/indexmap) and [notify](https://github.com/notify-rs/notify).


Updates `cc` from 1.2.27 to 1.2.29
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.27...cc-v1.2.29)

Updates `indexmap` from 2.9.0 to 2.10.0
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.9.0...2.10.0)

Updates `notify` from 8.0.0 to 8.1.0
- [Release notes](https://github.com/notify-rs/notify/releases)
- [Changelog](https://github.com/notify-rs/notify/blob/main/CHANGELOG.md)
- [Commits](https://github.com/notify-rs/notify/compare/notify-8.0.0...notify-8.1.0)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.29
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: indexmap
  dependency-version: 2.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
- dependency-name: notify
  dependency-version: 8.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-08 11:19:24 +00:00
Antonin Delpeuch 1a3b0375fa Fix 'extra' field generation for node-types.json 2025-07-07 21:15:09 -04:00
Antonin Delpeuch 59bcffe83b Add test demonstrating failure to populate 'extra'
The test is currently failing, will be fixed by the next commit.
2025-07-07 21:15:09 -04:00
Will Lillis 45318922d8 fix(xtask): run generate crate unit tests 2025-07-06 17:05:21 -04:00
Will Lillis 1a2167b155 fix(ci): run generate crate unit tests 2025-07-06 17:05:21 -04:00
WillLillis 8938309f4b fix(rust): ignore new nightly lint, correct order of lint list 2025-07-06 12:45:15 -04:00
WillLillis 51a800b7df fix(docs): don't show mdbook help popup when using code editor 2025-07-01 18:23:38 -04:00
Christian Clason a6cd6abcfb ci(nvim-ts): update paths to refactor
Problem: After https://github.com/tree-sitter/tree-sitter/pull/4496, the
crater tests from nvim-treesitter no longer run on PRs with relevant changes.

Solution: Update the paths in the workflows to the new structure.

Also throttle the build step to 10 parallel tasks (reduces flakiness on
Windows).

Also raise the timeout limit to 30 minutes (it's more expensive to have
to restart flaky tests than to let them run).
2025-06-27 16:34:17 +02:00
Veesh Goldman b09a15eb54 fix(cli): require correct setuptools version 2025-06-27 14:10:30 +02:00
Will Lillis a9818e4b17 fix(generate): only display conflicting symbol name in non-terminal
word token error message if available
2025-06-26 00:55:03 -04:00
Will Lillis be8b3e282a fix(rust): remove unused Command import 2025-06-26 00:54:44 -04:00
dependabot[bot] 259e34435f build(deps): bump brace-expansion in /crates/cli/eslint
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 09:30:13 +02:00
dependabot[bot] eaa10b279f build(deps): bump the cargo group with 4 updates
Bumps the cargo group with 4 updates: [cc](https://github.com/rust-lang/cc-rs), [memchr](https://github.com/BurntSushi/memchr), [webbrowser](https://github.com/amodm/webbrowser-rs) and [ureq](https://github.com/algesten/ureq).


Updates `cc` from 1.2.26 to 1.2.27
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.26...cc-v1.2.27)

Updates `memchr` from 2.7.4 to 2.7.5
- [Commits](https://github.com/BurntSushi/memchr/compare/2.7.4...2.7.5)

Updates `webbrowser` from 1.0.4 to 1.0.5
- [Release notes](https://github.com/amodm/webbrowser-rs/releases)
- [Changelog](https://github.com/amodm/webbrowser-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amodm/webbrowser-rs/compare/v1.0.4...v1.0.5)

Updates `ureq` from 3.0.11 to 3.0.12
- [Changelog](https://github.com/algesten/ureq/blob/main/CHANGELOG.md)
- [Commits](https://github.com/algesten/ureq/compare/3.0.11...3.0.12)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.27
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: memchr
  dependency-version: 2.7.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: webbrowser
  dependency-version: 1.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: ureq
  dependency-version: 3.0.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-17 08:18:16 +00:00
dependabot[bot] 8a15b881fd build(deps): bump esbuild and tsx in /lib/binding_web
Bumps [esbuild](https://github.com/evanw/esbuild) to 0.25.0 and updates ancestor dependency [tsx](https://github.com/privatenumber/tsx). These dependencies need to be updated together.


Updates `esbuild` from 0.23.1 to 0.25.0
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.23.1...v0.25.0)

Updates `tsx` from 4.19.2 to 4.19.3
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.19.2...v4.19.3)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.25.0
  dependency-type: indirect
- dependency-name: tsx
  dependency-version: 4.19.3
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-15 12:48:55 +03:00
Brian Schwind f475182c7d
fix(playground): initialize tree nodes with a 'plain' class 2025-06-15 12:45:55 +03:00
sammyne 4c51f27b0a feat(cli): support selecting bindings in init cmd 2025-06-15 12:43:03 +03:00
Tamir Bahar 64760ffa76
fix(web): fix typing for Parser.init
Since we're usually only providing `locateFile`, we need the type to be
`Partial<>` to allow it.

This also matches the typing in `@types/emscripten`'s
`EmscriptenModuleFactory` type signature.
2025-06-15 12:41:21 +03:00
Andrew Dupont 02fff92b91 Fix type definitions in exports
…and generate `.d.cts` files for CommonJS exports.
2025-06-15 09:46:05 +03:00
ObserverOfTime 876d5a96bf chore: use issue types in templates 2025-06-14 11:27:29 +03:00
laur89 e8d3d01598
docs: fix CLI link in readme (#4514) 2025-06-13 11:11:44 -07:00
James McCoy 889015f03b
build(rust): use $CARGO_PKG_RUST_VERSION when generating bindings (#4512)
Since cargo 1.63, $CARGO_PKG_RUST_VERSION is set in the build
environment to the value of the rust-version Cargo.toml field.

This removes the need to manually invoke cargo from build.rs during a
build of the tree-sitter crate with the bindgen feature enabled.

Removing the cargo invocation also ensures the build doesn't write to
the current directory when the target directory has been redirected
elsewhere. "cargo metadata" will attempt to update Cargo.lock, which
will fail if the source tree is read-only.
2025-06-13 11:11:22 -07:00
ObserverOfTime a2c98b4b5f
fix(loader): fix no-default-features build (#4505) 2025-06-11 09:00:54 -07:00
dependabot[bot] 46309a1f95 build(deps): bump the cargo group with 8 updates
Bumps the cargo group with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [anstyle](https://github.com/rust-cli/anstyle) | `1.0.10` | `1.0.11` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.25` | `1.2.26` |
| [clap](https://github.com/clap-rs/clap) | `4.5.39` | `4.5.40` |
| [clap_complete](https://github.com/clap-rs/clap) | `4.5.52` | `4.5.54` |
| [clap_complete_nushell](https://github.com/clap-rs/clap) | `4.5.6` | `4.5.7` |
| [flate2](https://github.com/rust-lang/flate2-rs) | `1.1.1` | `1.1.2` |
| [toml](https://github.com/toml-rs/toml) | `0.8.22` | `0.8.23` |
| [bindgen](https://github.com/rust-lang/rust-bindgen) | `0.71.1` | `0.72.0` |


Updates `anstyle` from 1.0.10 to 1.0.11
- [Commits](https://github.com/rust-cli/anstyle/compare/v1.0.10...v1.0.11)

Updates `cc` from 1.2.25 to 1.2.26
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.25...cc-v1.2.26)

Updates `clap` from 4.5.39 to 4.5.40
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.39...clap_complete-v4.5.40)

Updates `clap_complete` from 4.5.52 to 4.5.54
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.52...clap_complete-v4.5.54)

Updates `clap_complete_nushell` from 4.5.6 to 4.5.7
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete_nushell-v4.5.6...clap_complete_nushell-v4.5.7)

Updates `flate2` from 1.1.1 to 1.1.2
- [Release notes](https://github.com/rust-lang/flate2-rs/releases)
- [Commits](https://github.com/rust-lang/flate2-rs/compare/1.1.1...1.1.2)

Updates `toml` from 0.8.22 to 0.8.23
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.22...toml-v0.8.23)

Updates `bindgen` from 0.71.1 to 0.72.0
- [Release notes](https://github.com/rust-lang/rust-bindgen/releases)
- [Changelog](https://github.com/rust-lang/rust-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/rust-bindgen/compare/v0.71.1...v0.72.0)

---
updated-dependencies:
- dependency-name: anstyle
  dependency-version: 1.0.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: cc
  dependency-version: 1.2.26
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-version: 4.5.40
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete
  dependency-version: 4.5.54
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete_nushell
  dependency-version: 4.5.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: flate2
  dependency-version: 1.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: toml
  dependency-version: 0.8.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: bindgen
  dependency-version: 0.72.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-10 09:10:17 +02:00
Will Lillis e1f6e38b57
fix(xtask): limit test command to a single thread on windows (#4489) 2025-06-08 09:39:55 -07:00
ObserverOfTime 9772270868
fix(bindings): update swift & node dependencies (#4432)
* fix(bindings): update swift & node dependencies

also add the dependency name to Package.swift

* chore: update config schema
2025-06-07 11:41:07 -07:00
Max Brunsfeld 0fdf569571
Reorganize rust crates into a flat crates directory, simplify some CI steps (#4496)
* Move all rust crates (except lib) into crates dir, w/o nesting

* Remove stale path from .gitattributes

* Rename lib.rs files for easier navigation

* Rename mod.rs file for easier navigation

* Fix emscripten-version path

* Fix fixtures dir paths

* Use the default rustfmt settings

* Don't use nightly on CI
2025-06-06 14:25:37 -07:00
James McCoy a6e530b33d fix(test): remove period in test_flatten_grammar_with_recursive_inline_variable
The period was dropped in the `thiserror` refactor
(79444e07f9), which caused the
`test_flatten_grammar_with_recursive_inline_variable` test to fail.

Signed-off-by: James McCoy <jamessan@jamessan.com>
2025-06-06 10:35:02 +02:00
Riley Bruins 6cabd9e67f fix(query)!: assert that predicates end in ! or ?
Predicates/directives are documented to end in either `!` or `?`.
However, `query.c` allows them to be any valid identifier, and also
allows `?` or `!` characters anywhere inside an identifier.

This commit removes `?` and `!` from the list of valid identifier
characters, and asserts that predicates/directives only *end* in `?` or
`!`, respectively.

This commit is breaking because you can no longer do something like
`(#eq? @capture foo!bar)` (`foo!bar` must now be quoted).
2025-06-06 10:34:00 +02:00
Will Lillis ac13c86675 fix(cli): use a generic Into<Path> over single type for path arguments
Co-authored-by: Amaan Qureshi <amaanq12@gmail.com>
2025-06-05 02:41:18 -04:00
Will Lillis 50eaf5befd fix(query): correct expected and actual capture names in query assertions error message 2025-06-05 02:41:18 -04:00
Will Lillis 0220d9d93d feat(cli): add grammar-path to init, test, version, fuzz,
`query`, `highlight` and `tags` subcommands

Allows users to run various commands on a grammar without
being inside the directory for said grammar.
2025-06-05 02:41:18 -04:00
Antonin Delpeuch 88ac3abaa1
docs(highlight): update README with the new way to load languages 2025-06-05 02:41:00 -04:00
Dan Jacka ad133ecb38
docs: fix indentation & formatting 2025-06-05 01:53:35 -04:00
Thalia Archibald 6ba73fd888
fix(highlight): account for carriage return at EOF and chunk ends 2025-06-05 01:48:01 -04:00
Will Lillis 8bd923ab9e fix(lib): replace raw array accesses with array_get 2025-06-05 00:53:11 -04:00
Olive Easton 50622f71f8 fix(generate): re-enable default url features 2025-06-04 10:29:59 +02:00
Max Brunsfeld 2ab9c9b590
Fully fix field underflow in go_to_previous_sibling (#4483)
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-06-02 15:34:25 -07:00
dependabot[bot] 52b719f8fb build(deps): bump the cargo group with 5 updates
Bumps the cargo group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.23` | `1.2.25` |
| [clap](https://github.com/clap-rs/clap) | `4.5.38` | `4.5.39` |
| [clap_complete](https://github.com/clap-rs/clap) | `4.5.50` | `4.5.52` |
| [clap_complete_nushell](https://github.com/clap-rs/clap) | `4.5.5` | `4.5.6` |
| [libloading](https://github.com/nagisa/rust_libloading) | `0.8.7` | `0.8.8` |


Updates `cc` from 1.2.23 to 1.2.25
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.23...cc-v1.2.25)

Updates `clap` from 4.5.38 to 4.5.39
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.38...clap_complete-v4.5.39)

Updates `clap_complete` from 4.5.50 to 4.5.52
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.50...clap_complete-v4.5.52)

Updates `clap_complete_nushell` from 4.5.5 to 4.5.6
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete_nushell-v4.5.5...clap_complete_nushell-v4.5.6)

Updates `libloading` from 0.8.7 to 0.8.8
- [Commits](https://github.com/nagisa/rust_libloading/commits)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-version: 4.5.39
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete
  dependency-version: 4.5.52
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete_nushell
  dependency-version: 4.5.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: libloading
  dependency-version: 0.8.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 23:56:03 +02:00
zyoshoka 135eeded02 docs: update broken link in the playground page 2025-05-31 21:19:20 -04:00
Kai Pastor 0bdf698673
fix(build): pkgconfig and use of GNUInstallDirs (#4319)
* Fix pkgconfig

Init CMAKE_INSTALL_INCLUDEDIR before pc file generation.
Install pc file to CMAKE_INSTALL_LIBDIR/pkgconfig -
it accompanies the architecture-dependent library.

* Include GNUInstallDirs early

The CMake module initializes variables which are used for
exported information (CMake and pkgconfig).

* Change pc file install destination
2025-05-31 11:45:35 +02:00
Christian Clason e7f9160867
Revert "feat: add build sha to parser.c header comment" (#4475)
This reverts commit dc4e232e6e.

Reason: The sha in the generated output (which most distro builds of
tree-sitter, including `cargo install`, strip) produces too many
conflicts when verifying via CI that parsers are regenerated on every
grammar change.
2025-05-29 11:47:06 -07:00
Max Brunsfeld ca64399f9f Ignore lock files in grammar repos
It is very common practice to ignore
these lock files for libraries, since they do not apply to applications
that use the libraries. The lock files are especially not useful in
tree-sitter grammar repos, since tree-sitter grammars should not have
dependencies. The lock files are just a source of merge conflicts and
spurious CI failures.
2025-05-28 10:58:45 -07:00
sogaiu 19148eaa0d docs: mention ~ and $HOME for parser-directories 2025-05-28 11:21:09 +02:00
Max Brunsfeld b8ba76b1ae Fix hang in npm install script 2025-05-27 17:35:23 -07:00
Max Brunsfeld f91255a201
Fix crash w/ goto_previous_sibling when parent node has leading extra child (#4472)
* Fix crash w/ goto_previous_sibling when parent node has leading extra
child Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>

* Fix lint

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-05-27 16:56:33 -07:00
Haoxiang Fei 06537fda83 fix: wasi has endian.h 2025-05-24 12:27:13 +02:00
dependabot[bot] 299f8a9fb9 build(deps): bump cc from 1.2.22 to 1.2.23 in the cargo group
Bumps the cargo group with 1 update: [cc](https://github.com/rust-lang/cc-rs).


Updates `cc` from 1.2.22 to 1.2.23
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.22...cc-v1.2.23)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-20 08:07:08 +02:00
Mike Zeller 4339b0fe05 illumos has endian.h 2025-05-15 09:53:45 +02:00
dependabot[bot] 08bb6f44a4 build(deps): bump the cargo group with 6 updates
Bumps the cargo group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.21` | `1.2.22` |
| [clap](https://github.com/clap-rs/clap) | `4.5.37` | `4.5.38` |
| [clap_complete](https://github.com/clap-rs/clap) | `4.5.48` | `4.5.50` |
| [ctrlc](https://github.com/Detegr/rust-ctrlc) | `3.4.6` | `3.4.7` |
| [libloading](https://github.com/nagisa/rust_libloading) | `0.8.6` | `0.8.7` |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.19.1` | `3.20.0` |


Updates `cc` from 1.2.21 to 1.2.22
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.21...cc-v1.2.22)

Updates `clap` from 4.5.37 to 4.5.38
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.37...clap_complete-v4.5.38)

Updates `clap_complete` from 4.5.48 to 4.5.50
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.48...clap_complete-v4.5.50)

Updates `ctrlc` from 3.4.6 to 3.4.7
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/compare/3.4.6...3.4.7)

Updates `libloading` from 0.8.6 to 0.8.7
- [Commits](https://github.com/nagisa/rust_libloading/compare/0.8.6...0.8.7)

Updates `tempfile` from 3.19.1 to 3.20.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.19.1...v3.20.0)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-version: 4.5.38
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete
  dependency-version: 4.5.50
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: ctrlc
  dependency-version: 3.4.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: libloading
  dependency-version: 0.8.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: tempfile
  dependency-version: 3.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-13 08:12:35 +02:00
Will Lillis 31b9717ca3 fix(lib): return early for empty predicate step slice 2025-05-11 08:57:30 -04:00
dependabot[bot] 52a792384f build(deps): bump the cargo group with 2 updates
Bumps the cargo group with 2 updates: [cc](https://github.com/rust-lang/cc-rs) and [git2](https://github.com/rust-lang/git2-rs).


Updates `cc` from 1.2.20 to 1.2.21
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.20...cc-v1.2.21)

Updates `git2` from 0.20.1 to 0.20.2
- [Changelog](https://github.com/rust-lang/git2-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/git2-rs/compare/git2-0.20.1...git2-0.20.2)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: git2
  dependency-version: 0.20.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-06 08:57:38 +00:00
Will Lillis b1d2b7cfb8 fix(query): correct last_child_step_index in cases where a new step
wasn't created.

This fixes an OOB access to `self.steps` when a last child anchor
immediately follows a predicate.
2025-05-03 17:27:37 -04:00
Will Lillis cc634236b1 fix(rust): address new clippy lint 2025-05-03 19:57:11 +02:00
Will Lillis 91274f47e4 fix(rust): ignore obfuscated_if_else lint 2025-05-03 19:57:11 +02:00
dependabot[bot] bfc5d1180c build(deps): bump vite from 6.3.2 to 6.3.4 in /lib/binding_web
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.3.2 to 6.3.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.3.4/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.3.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-01 09:18:47 +02:00
Amaan Qureshi 21c658a12c fix(lib): do not access the alias sequence for the end subtree in ts_subtree_summarize_children 2025-04-28 23:13:13 -04:00
dependabot[bot] e057299b0d build(deps): bump the cargo group with 3 updates
Bumps the cargo group with 3 updates: [cc](https://github.com/rust-lang/cc-rs), [clap_complete](https://github.com/clap-rs/clap) and [toml](https://github.com/toml-rs/toml).


Updates `cc` from 1.2.19 to 1.2.20
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.19...cc-v1.2.20)

Updates `clap_complete` from 4.5.47 to 4.5.48
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.47...clap_complete-v4.5.48)

Updates `toml` from 0.8.20 to 0.8.22
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.20...toml-v0.8.22)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete
  dependency-version: 4.5.48
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: toml
  dependency-version: 0.8.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-28 19:43:22 -04:00
Amaan Qureshi 3056dc5be4 fix(generate): mark url as a Windows-only dependency 2025-04-28 18:31:09 -04:00
Amaan Qureshi 0191e94226 docs(rust): remove adding cc as a build dependency for consumers 2025-04-28 18:30:45 -04:00
Will Lillis dcdd5bc372
fix(parse): explicitly move temporaries in the logger callback
This fixes problems where these stack-local temporaries are used after their scope ends.
2025-04-28 03:30:05 -04:00
Will Lillis d0e0e0322c
fix(xtask): properly trim emscripten version at the source (#4402)
* fix(xtask): properly trim emscripten version at the source

* fix(loader): trim emscripten version in `cargo:rustc-env=` print
2025-04-27 20:59:04 -07:00
Max Brunsfeld ca7ff033db
Fix wasm build issues introduced by switching to wasi-sdk (#4407)
* Don't shell out for extracting tar.gz files

* Avoid wasi-sdk adding dependency on libc.so

* Clippy

* Fix -nostdlib flag
2025-04-27 19:42:34 -07:00
Max Brunsfeld d4d8ed32b3
cli: Compile parsers to wasm using wasi-sdk, not emscripten (#4393)
* Build wasm modules using wasi-sdk, not emscripten

* Log a warning if --docker is passed
2025-04-24 16:00:09 -07:00
Max Brunsfeld 635c49909c Add zed config file (enable all cargo features for rust-analyzer) 2025-04-23 16:32:21 -07:00
Tamchuk 70cf2a7a22
docs: fix typo 2025-04-23 08:26:51 -04:00
dependabot[bot] 74e6641afc build(deps): bump the cargo group with 2 updates
Bumps the cargo group with 2 updates: [clap](https://github.com/clap-rs/clap) and [ureq](https://github.com/algesten/ureq).


Updates `clap` from 4.5.36 to 4.5.37
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.36...clap_complete-v4.5.37)

Updates `ureq` from 3.0.10 to 3.0.11
- [Changelog](https://github.com/algesten/ureq/blob/main/CHANGELOG.md)
- [Commits](https://github.com/algesten/ureq/compare/3.0.10...3.0.11)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.37
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: ureq
  dependency-version: 3.0.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-22 10:13:43 +02:00
Will Lillis c7475e4bf3 perf(highlight): use BTreeMap over IndexMap for highlight configs 2025-04-20 05:55:37 -04:00
WillLillis eee41925aa fix(ci): increase timeouts for flaky tests 2025-04-19 10:59:09 +02:00
Amaan Qureshi 69d553c82a style(cli): formatting 2025-04-19 01:15:53 -04:00
dependabot[bot] 043e0c65ec build(deps): bump vite from 6.1.0 to 6.2.6 in /lib/binding_web
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.1.0 to 6.2.6.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.6/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.2.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-18 23:57:56 -04:00
vemoo 4dffb818e2
feat(web): export wasm files to better support bundling use cases 2025-04-18 23:47:27 -04:00
Daniel Jalkut 4514751803
docs(cli): improve documentation for the edits argument when parsing code 2025-04-18 23:45:52 -04:00
Tamir Bahar 27fa1088b9
fix(web): replace dynamic require with import 2025-04-18 23:40:40 -04:00
MichiRecRoom 853ca46899
docs(rust): improve bindings' crate doc 2025-04-18 22:55:59 -04:00
Riley Bruins 21390af2dd fix(web): correct childWithDescendant() functionality
This fix allows for more granular address control when marshalling nodes
across WASM. This is necessary for node methods which accept another
node as a parameter (i.e., `childWithDescendant()`)
2025-04-18 18:11:42 -04:00
Will Lillis 45a281c962 fix(ci): trigger build_wasm workflow when xtask is modified 2025-04-16 18:06:48 -04:00
Will Lillis c7c1614278 fix(xtask): trim EMSCRIPTEN_TAG arg to docker command in build-wasm
command
2025-04-16 18:06:48 -04:00
dependabot[bot] c085a772cf build(deps): bump the cargo group with 4 updates
Bumps the cargo group with 4 updates: [anyhow](https://github.com/dtolnay/anyhow), [bstr](https://github.com/BurntSushi/bstr), [cc](https://github.com/rust-lang/cc-rs) and [clap](https://github.com/clap-rs/clap).


Updates `anyhow` from 1.0.97 to 1.0.98
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.97...1.0.98)

Updates `bstr` from 1.11.3 to 1.12.0
- [Commits](https://github.com/BurntSushi/bstr/compare/1.11.3...1.12.0)

Updates `cc` from 1.2.18 to 1.2.19
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.18...cc-v1.2.19)

Updates `clap` from 4.5.35 to 4.5.36
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.35...clap_complete-v4.5.36)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.98
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: bstr
  dependency-version: 1.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
- dependency-name: cc
  dependency-version: 1.2.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-version: 4.5.36
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-15 11:38:47 +02:00
Will Lillis 9fdf685dd5 fix(bindings): report wasm store error in rust bindings set_language 2025-04-14 22:15:40 -04:00
Riley Bruins 733d7513af fix(lib): reset parser options after use
**Problem:** After `ts_parser_parse_with_options()`, the parser options
are still stored in the parser object, meaning that a successive call to
`ts_parser_parse()` will actually behave like
`ts_parser_parse_with_options()`, which is not obvious and can have
unintended consequences.

**Solution:** Reset to empty options state after
`ts_parser_parse_with_options()`.
2025-04-14 21:35:40 -04:00
Paul Gey b341073192 Make highlighting more deterministic when themes are ambiguous 2025-04-10 23:04:00 -04:00
Jon Shea 92c5d3b8e2 fix(rust): clarify error message for non-token reserved words
Improve the `NonTokenReservedWord` error message by including the
specific reserved word that was not used as a token.
2025-04-09 18:15:48 -04:00
Jason Boatman abc5c6bc50
Fix WASI build by not calling a non-existent function. (#4343) 2025-04-08 09:33:48 -07:00
Edgar Onghena 52d2865365
chore(generate): add @generated to parser.c header (#4338)
This makes `parser.c` follow the https://generated.at/ convention for generated files. This potentially allows any compatible IDE to discourage editing it directly.
2025-04-08 10:52:37 +02:00
dependabot[bot] 89b04babfb ci: bump actions/create-github-app-token in the actions group
Bumps the actions group with 1 update: [actions/create-github-app-token](https://github.com/actions/create-github-app-token).


Updates `actions/create-github-app-token` from 1 to 2
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](https://github.com/actions/create-github-app-token/compare/v1...v2)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 10:18:03 +02:00
dependabot[bot] 755f77231c build(deps): bump the cargo group across 1 directory with 5 updates
Bumps the cargo group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.17` | `1.2.18` |
| [clap](https://github.com/clap-rs/clap) | `4.5.32` | `4.5.35` |
| [ctrlc](https://github.com/Detegr/rust-ctrlc) | `3.4.5` | `3.4.6` |
| [indexmap](https://github.com/indexmap-rs/indexmap) | `2.8.0` | `2.9.0` |
| [once_cell](https://github.com/matklad/once_cell) | `1.21.1` | `1.21.3` |



Updates `cc` from 1.2.17 to 1.2.18
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.17...cc-v1.2.18)

Updates `clap` from 4.5.32 to 4.5.35
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.32...clap_complete-v4.5.35)

Updates `ctrlc` from 3.4.5 to 3.4.6
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/compare/3.4.5...3.4.6)

Updates `indexmap` from 2.8.0 to 2.9.0
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.8.0...2.9.0)

Updates `once_cell` from 1.21.1 to 1.21.3
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.21.1...v1.21.3)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-version: 4.5.35
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: ctrlc
  dependency-version: 3.4.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: indexmap
  dependency-version: 2.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
- dependency-name: once_cell
  dependency-version: 1.21.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 10:17:45 +02:00
Bernardo Sulzbach 3d4d13ea1e Fix typo of "corresponds" 2025-04-07 18:34:12 -04:00
Peter Hill 4ec8aacaec docs: Fix link to named-vs-anonymous-nodes section 2025-04-06 10:58:42 -04:00
Will Lillis 0f949168ef fix(cli): display "N/A" in parse stats where appropriate when no parsing
took place
2025-04-06 09:45:50 -04:00
Will Lillis 71941d8bda fix(highlight): account for multiple rows in highlight testing assertions 2025-04-06 09:45:50 -04:00
WillLillis 521da2b0a7 fix(rust): address new nightly lint for pointer comparisons 2025-03-26 08:58:50 +01:00
Christian Clason 37a9ecd5b7 build(deps): update emscripten to v4.0.5 2025-03-25 15:18:44 +01:00
Simon Willshire ee8d529552
fix(rust): use core crates for no_std
also add `no_std` build to CI
2025-03-25 14:30:32 +01:00
WillLillis dac6300558 fix(rust): address new clippy lint for pointer comparisons 2025-03-25 13:44:42 +01:00
dependabot[bot] d05e4ae7ff build(deps): bump the cargo group across 1 directory with 8 updates
Bumps the cargo group with 8 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.16` | `1.2.17` |
| [clap_complete](https://github.com/clap-rs/clap) | `4.5.46` | `4.5.47` |
| [git2](https://github.com/rust-lang/git2-rs) | `0.20.0` | `0.20.1` |
| [log](https://github.com/rust-lang/log) | `0.4.26` | `0.4.27` |
| [once_cell](https://github.com/matklad/once_cell) | `1.21.0` | `1.21.1` |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.18.0` | `3.19.1` |
| [widestring](https://github.com/starkat99/widestring-rs) | `1.1.0` | `1.2.0` |
| [ureq](https://github.com/algesten/ureq) | `3.0.8` | `3.0.10` |



Updates `cc` from 1.2.16 to 1.2.17
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.16...cc-v1.2.17)

Updates `clap_complete` from 4.5.46 to 4.5.47
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.46...clap_complete-v4.5.47)

Updates `git2` from 0.20.0 to 0.20.1
- [Changelog](https://github.com/rust-lang/git2-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/git2-rs/compare/git2-0.20.0...git2-0.20.1)

Updates `log` from 0.4.26 to 0.4.27
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.26...0.4.27)

Updates `once_cell` from 1.21.0 to 1.21.1
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.21.0...v1.21.1)

Updates `tempfile` from 3.18.0 to 3.19.1
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.18.0...v3.19.1)

Updates `widestring` from 1.1.0 to 1.2.0
- [Release notes](https://github.com/starkat99/widestring-rs/releases)
- [Changelog](https://github.com/VoidStarKat/widestring-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/starkat99/widestring-rs/compare/v1.1.0...v1.2.0)

Updates `ureq` from 3.0.8 to 3.0.10
- [Changelog](https://github.com/algesten/ureq/blob/main/CHANGELOG.md)
- [Commits](https://github.com/algesten/ureq/compare/3.0.8...3.0.10)

---
updated-dependencies:
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: git2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: log
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
- dependency-name: widestring
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
- dependency-name: ureq
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-25 12:24:08 +01:00
JacobCrabill e3db212b0b build.zig.zon: Fix package hashes for Zig 0.14
Zig 0.14 changed how package hashes are computed and used, and if the
old package hashes are left, every call to `zig build` will re-download
every package every time.  Updating to the new hash format solves this.
2025-03-21 17:33:38 +01:00
WillLillis f0e7ac2f18 chore(cli): remove deprecated --no-bindings flag from generate command 2025-03-16 11:04:29 +01:00
dependabot[bot] e08fe3b993 build(deps): bump the cargo group across 1 directory with 21 updates
Bumps the cargo group with 20 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.95` | `1.0.97` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.10` | `1.2.16` |
| [clap](https://github.com/clap-rs/clap) | `4.5.27` | `4.5.32` |
| [clap_complete](https://github.com/clap-rs/clap) | `4.5.42` | `4.5.46` |
| [indexmap](https://github.com/indexmap-rs/indexmap) | `2.7.1` | `2.8.0` |
| [indoc](https://github.com/dtolnay/indoc) | `2.0.5` | `2.0.6` |
| [log](https://github.com/rust-lang/log) | `0.4.25` | `0.4.26` |
| [once_cell](https://github.com/matklad/once_cell) | `1.20.2` | `1.21.0` |
| [rustc-hash](https://github.com/rust-lang/rustc-hash) | `2.1.0` | `2.1.1` |
| [semver](https://github.com/dtolnay/semver) | `1.0.25` | `1.0.26` |
| [serde](https://github.com/serde-rs/serde) | `1.0.217` | `1.0.219` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.137` | `1.0.140` |
| [smallbitvec](https://github.com/servo/smallbitvec) | `2.5.3` | `2.6.0` |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.15.0` | `3.18.0` |
| [thiserror](https://github.com/dtolnay/thiserror) | `2.0.11` | `2.0.12` |
| [toml](https://github.com/toml-rs/toml) | `0.8.19` | `0.8.20` |
| [unindent](https://github.com/dtolnay/indoc) | `0.2.3` | `0.2.4` |
| [wasmparser](https://github.com/bytecodealliance/wasm-tools) | `0.224.0` | `0.224.1` |
| [webbrowser](https://github.com/amodm/webbrowser-rs) | `1.0.3` | `1.0.4` |
| [ureq](https://github.com/algesten/ureq) | `3.0.0` | `3.0.8` |



Updates `anyhow` from 1.0.95 to 1.0.97
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.95...1.0.97)

Updates `cc` from 1.2.10 to 1.2.16
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.10...cc-v1.2.16)

Updates `clap` from 4.5.27 to 4.5.32
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.27...clap_complete-v4.5.32)

Updates `clap_complete` from 4.5.42 to 4.5.46
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.42...clap_complete-v4.5.46)

Updates `indexmap` from 2.7.1 to 2.8.0
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.7.1...2.8.0)

Updates `indoc` from 2.0.5 to 2.0.6
- [Release notes](https://github.com/dtolnay/indoc/releases)
- [Commits](https://github.com/dtolnay/indoc/compare/2.0.5...2.0.6)

Updates `log` from 0.4.25 to 0.4.26
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.25...0.4.26)

Updates `once_cell` from 1.20.2 to 1.21.0
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.20.2...v1.21.0)

Updates `rustc-hash` from 2.1.0 to 2.1.1
- [Changelog](https://github.com/rust-lang/rustc-hash/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/rustc-hash/compare/v2.1.0...v2.1.1)

Updates `semver` from 1.0.25 to 1.0.26
- [Release notes](https://github.com/dtolnay/semver/releases)
- [Commits](https://github.com/dtolnay/semver/compare/1.0.25...1.0.26)

Updates `serde` from 1.0.217 to 1.0.219
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.217...v1.0.219)

Updates `serde_derive` from 1.0.217 to 1.0.219
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.217...v1.0.219)

Updates `serde_json` from 1.0.137 to 1.0.140
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.137...v1.0.140)

Updates `smallbitvec` from 2.5.3 to 2.6.0
- [Release notes](https://github.com/servo/smallbitvec/releases)
- [Commits](https://github.com/servo/smallbitvec/compare/v2.5.3...v2.6.0)

Updates `tempfile` from 3.15.0 to 3.18.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.15.0...v3.18.0)

Updates `thiserror` from 2.0.11 to 2.0.12
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.11...2.0.12)

Updates `toml` from 0.8.19 to 0.8.20
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.19...toml-v0.8.20)

Updates `unindent` from 0.2.3 to 0.2.4
- [Release notes](https://github.com/dtolnay/indoc/releases)
- [Commits](https://github.com/dtolnay/indoc/compare/0.2.3...0.2.4)

Updates `wasmparser` from 0.224.0 to 0.224.1
- [Release notes](https://github.com/bytecodealliance/wasm-tools/releases)
- [Commits](https://github.com/bytecodealliance/wasm-tools/commits)

Updates `webbrowser` from 1.0.3 to 1.0.4
- [Release notes](https://github.com/amodm/webbrowser-rs/releases)
- [Changelog](https://github.com/amodm/webbrowser-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amodm/webbrowser-rs/compare/v1.0.3...v1.0.4)

Updates `ureq` from 3.0.0 to 3.0.8
- [Changelog](https://github.com/algesten/ureq/blob/main/CHANGELOG.md)
- [Commits](https://github.com/algesten/ureq/compare/3.0.0...3.0.8)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
- dependency-name: indoc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: log
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
- dependency-name: rustc-hash
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: semver
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: serde_derive
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: smallbitvec
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: unindent
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: wasmparser
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: webbrowser
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
- dependency-name: ureq
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-15 16:43:49 +01:00
Tamir Bahar a380e1a259
refactor(web): change return types to Node[] in child-related methods 2025-03-15 09:09:31 -04:00
Peter Oliver 17471bdfcc
fix(build): make install shouldn’t fail when a parser bundles no queries (#4284) 2025-03-14 09:37:51 +01:00
WillLillis 5985690d45 fix(xtask): error if new version supplied to bump-version is less than
or equal to current version
2025-03-14 09:37:25 +01:00
Christian Clason 07a1b7fc00 fix(build): bump version to 0.26.0
Some checks failed
CI / sanitize (push) Failing after 11s
CI / build (push) Failing after 12s
CI / checks (push) Has been cancelled
Check WASM Exports / check-wasm-exports (push) Has been cancelled
After a release, `master` branch should be bumped to the next _minor_ version.
2025-03-12 14:52:09 +01:00
dependabot[bot] 12aff698b9 build(deps): bump emscripten to 4.0.4
Some checks failed
CI / checks (push) Waiting to run
CI / sanitize (push) Failing after 12s
CI / build (push) Failing after 12s
2025-03-12 09:30:11 +00:00
dependabot[bot] 276accc210 build(deps): bump ring from 0.17.8 to 0.17.13
Bumps [ring](https://github.com/briansmith/ring) from 0.17.8 to 0.17.13.
- [Changelog](https://github.com/briansmith/ring/blob/main/RELEASES.md)
- [Commits](https://github.com/briansmith/ring/commits)

---
updated-dependencies:
- dependency-name: ring
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-12 09:30:11 +00:00
Riley Bruins cc3994928c feat(lib): quote invalid nodes, fields, and captures 2025-03-12 10:28:57 +01:00
WillLillis efd212ee46 fix(cli): properly escape invisible characters in parse error output
Some checks failed
CI / checks (push) Waiting to run
CI / sanitize (push) Failing after 12s
CI / build (push) Failing after 12s
2025-03-11 17:27:12 -04:00
Jon Shea ec3a889758 fix(docs): update test command flag from -f to -i
Some checks failed
CI / sanitize (push) Failing after 12s
CI / build (push) Failing after 12s
CI / checks (push) Has been cancelled
Deploy Docs / deploy-docs (push) Has been cancelled
The `-f` flag on `tree-sitter test` seems to have been removed or
replaced with a `-i, --include` flag. This PR updates the "Writing Tests"
page to reflect this change.
2025-03-10 11:18:59 -04:00
Niklas Koll 1850762118
feat(build): update build.zig.zon for zig 0.14 2025-03-10 15:56:41 +01:00
June Gschwantner 0b28226615
docs(bindings): Add "Using WASM Grammar files" section to binding_rust (#4235)
Some checks failed
CI / sanitize (push) Failing after 12s
CI / build (push) Failing after 12s
Check Bindgen Output / check-bindgen (push) Has been cancelled
CI / checks (push) Has been cancelled
Check WASM Exports / check-wasm-exports (push) Has been cancelled
2025-03-06 14:15:21 -08:00
NOT XVilka a00fab7dc4
fix(lib): remove duplicate TSLanguageMetadata typedef (#4268) 2025-03-06 14:14:25 -08:00
WillLillis 11071ed682 fix(rust): adapt to new nightly lint
Some checks failed
CI / checks (push) Waiting to run
CI / sanitize (push) Failing after 12s
CI / build (push) Failing after 12s
2025-03-06 09:32:21 +01:00
Max Brunsfeld 066fd77d39
Fix cases where error recovery could infinite loop (#4257)
Some checks failed
CI / sanitize (push) Failing after 12s
CI / build (push) Failing after 12s
CI / checks (push) Has been cancelled
* Rename corpus test functions to allow easy filtering by language

* Use usize for seed argument

* Avoid retaining useless stack versions when reductions merge

We found this problem when debugging an infinite loop that happened
during error recovery when using the Zig grammar. The large number of
unnecessary paused stack versions were preventing the correct recovery
strategy from being tried.

* Fix leaked lookahead token when reduction results in a merged stack

* Enable running PHP tests in CI

* Fix possible infinite loop during error recovery at EOF

* Account for external scanner state changes when detecting changed ranges in subtrees
2025-03-04 13:50:56 -08:00
polazarus 8138dba800
fix(generate): mark TSCharacterRange as static (#4255)
Some checks are pending
CI / checks (push) Waiting to run
CI / sanitize (push) Waiting to run
CI / build (push) Waiting to run
Problem: Linking different parsers into one executable fails due to duplicate symbols.

Solution: Mark `TSCharacterRange` as `static` when generating parsers.

fixes #4209
2025-03-04 15:26:13 +01:00
Yuri Astrakhan b749c02eb5 Mark all format-like macros for Clippy
Some checks failed
CI / checks (push) Waiting to run
CI / sanitize (push) Waiting to run
CI / build (push) Waiting to run
Deploy Docs / deploy-docs (push) Has been cancelled
See https://doc.rust-lang.org/nightly/clippy/attribs.html#clippyformat_args

Ensure that usage of the macros with `format!`-like parameters will be validated by Clippy
2025-03-03 02:16:23 -05:00
WillLillis 1fdd1d250c ci: separate nightly lints to separate job 2025-03-02 16:37:03 -05:00
WillLillis 998fb34d15 fix(test): update expected tree-sitter-rust supertypes 2025-03-02 16:37:03 -05:00
WillLillis cb30ec5b17 fix(rust): adapt to some new nightly lints 2025-03-02 16:37:03 -05:00
Andrew Dunbar ab7eb70a3c
typo: but is only works → but it only works (#4242) 2025-02-26 09:29:27 -08:00
Will Lillis 3b67861def
fix(wasm): restore passing in ERROR to descendantsOfType (#4226)
Some checks failed
CI / sanitize (push) Failing after 11s
CI / build (push) Failing after 14s
CI / checks (push) Has been cancelled
2025-02-19 14:47:10 -08:00
Max Brunsfeld b26b7f8d62
Decrease the MSRV for the tree-sitter-language crate (#4221)
Some checks failed
CI / sanitize (push) Failing after 12s
CI / build (push) Failing after 14s
CI / checks (push) Has been cancelled
2025-02-17 18:52:57 -08:00
Max Brunsfeld 2bd400dcee
Reset result_symbol field of lexer in wasm memory in between invocations (#4218) 2025-02-17 17:36:46 -08:00
Max Brunsfeld dedcc5255a
Ignore external tokens that are zero-length and extra (#4213)
Co-authored-by: Anthony <anthony@zed.dev>
2025-02-17 15:07:44 -08:00
Max Brunsfeld 14b8ead412
Fix crash when loading languages w/ old ABI via wasm (#4210) 2025-02-17 13:56:53 -08:00
Antonin Décimo f0571b1e33
feat: Windows support using MinGW-w64 (#4201)
Some checks failed
CI / sanitize (push) Failing after 12s
CI / build (push) Failing after 14s
CI / checks (push) Has been cancelled
Building tree-sitter on Windows within a Cygwin or MSYS2 environment
for MinGW-w64 targets should work. As there's no configure-like step,
the tools have to be specified with:

    make CC=x86_64-w64-mingw32-gcc \
         AR=x86_64-w64-mingw32-ar \
         STRIP=x86_64-w64-mingw32-strip

Useful reference:
[How to build and use DLLs on Windows](https://nullprogram.com/blog/2021/05/31/)

This commit doesn't tag public functions with dllexport or
dllimport. This results in exporting non-static functions visible
between translation units, and generating slightly less efficient code
for calling exported functions from the DLL.

This commit doesn't include support to build libtree-sitter with MSVC or
clang-cl, but generates a libtree-sitter.lib file for MSVC/clang-cl
consumers.
2025-02-12 16:31:06 -08:00
Amaan Qureshi 8e79929cb8 ci: remove benchmark step
Some checks failed
CI / checks (push) Waiting to run
CI / sanitize (push) Failing after 11s
CI / build (push) Failing after 14s
Deploy Docs / deploy-docs (push) Has been cancelled
We don't actually use this data, it runs without a purpose
2025-02-11 22:56:50 -05:00
Amaan Qureshi 0a33135483 ci: rename web files, and upload a tarball for web-tree-sitter assets 2025-02-11 22:56:50 -05:00
Amaan Qureshi a40265cbeb refactor(web): rename tree-sitter.js to web-tree-sitter.js
This is not breaking for consumers of the web bindings, nor the
playground as both filenames will be kept in the .github.io repo
2025-02-11 22:56:50 -05:00
Amaan Qureshi 74d7ca8582 fix(rust): adapt to new clippy lints
Some checks failed
CI / sanitize (push) Failing after 11s
CI / build (push) Failing after 14s
CI / checks (push) Has been cancelled
2025-02-08 13:15:56 -05:00
Amaan Qureshi 1a80a1f413 fix: add generate crate to workspace members 2025-02-08 13:15:56 -05:00
Roberto Huertas f95e0e3a56 fix(web): provide type in the exports
When using TypeScript projects using other module settings than CommonJs, the types were not correctly exposed, and the compilation failed.

This adds the types path to the exports so compilation works for `module: NodeNext` and other variants.
2025-02-08 12:47:17 -05:00
Thomas Klausner 14647b2a38 build: add a comment explaining why we undef _POSIX_C_SOURCE
Some checks failed
CI / sanitize (push) Failing after 11s
CI / build (push) Failing after 13s
CI / checks (push) Has been cancelled
2025-02-02 17:14:28 -05:00
Thomas Klausner 5311904619 build: fix compilation on NetBSD a different way 2025-02-02 17:14:28 -05:00
Thomas Klausner 60b76c7834 Revert "build: do not define _POSIX_C_SOURCE on NetBSD"
This reverts commit 54921453da.
2025-02-02 17:14:28 -05:00
Thomas Klausner 2bf04d1f04 build: do not define _POSIX_C_SOURCE on NetBSD
It leads to missing symbols, see #4180.
2025-02-02 17:14:28 -05:00
556 changed files with 49420 additions and 28129 deletions

View file

@ -1,2 +0,0 @@
target
.git

View file

@ -10,6 +10,9 @@ insert_final_newline = true
[*.rs]
indent_size = 4
[*.{zig,zon}]
indent_size = 4
[Makefile]
indent_style = tab
indent_size = 8

4
.envrc Normal file
View file

@ -0,0 +1,4 @@
source_up_if_exists
if [ -z "${IN_NIX_SHELL:-}" ]; then
use flake .
fi

1
.gitattributes vendored
View file

@ -3,5 +3,4 @@
/lib/src/unicode/*.h linguist-vendored
/lib/src/unicode/LICENSE linguist-vendored
/cli/src/generate/prepare_grammar/*.json -diff
Cargo.lock -diff

View file

@ -1,7 +1,15 @@
name: Bug Report
description: Report a problem
labels: [bug]
type: Bug
body:
- type: checkboxes
attributes:
label: AI Policy
description: Review our [AI Policy](https://tree-sitter.github.io/tree-sitter/6-contributing.html#ai-policy).
options:
- label: I have read the AI Policy and this issue complies with it.
required: true
- type: textarea
attributes:
label: "Problem"

View file

@ -1,7 +1,15 @@
name: Feature request
description: Request an enhancement
labels: [enhancement]
type: Feature
body:
- type: checkboxes
attributes:
label: AI Policy
description: Review our [AI Policy](https://tree-sitter.github.io/tree-sitter/6-contributing.html#ai-policy).
options:
- label: I have read the AI Policy and this issue complies with it.
required: true
- type: markdown
attributes:
value: |

View file

@ -10,17 +10,20 @@ outputs:
runs:
using: composite
steps:
- uses: actions/cache@v4
- uses: actions/cache@v5
id: cache
with:
path: |
test/fixtures/grammars
target/release/tree-sitter-*.wasm
key: fixtures-${{ join(matrix.*, '_') }}-${{ hashFiles(
'cli/generate/src/**',
'crates/generate/src/**',
'lib/src/parser.h',
'lib/src/array.h',
'lib/src/alloc.h',
'xtask/src/*',
'lib/src/wasm-stdlib/external_scanner_stdlib.h',
'crates/loader/wasi-sdk-version',
'crates/loader/binaryen-version',
'test/fixtures/grammars/*/**/src/*.c',
'test/fixtures/fixtures.json',
'.github/actions/cache/action.yml') }}

20
.github/cliff.toml vendored
View file

@ -43,16 +43,16 @@ commit_preprocessors = [
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "!:", group = "<!-- 0 -->Breaking" },
{ message = "^feat", group = "<!-- 1 -->Features" },
{ message = "^fix", group = "<!-- 2 -->Bug Fixes" },
{ message = "^perf", group = "<!-- 3 -->Performance" },
{ message = "^doc", group = "<!-- 4 -->Documentation" },
{ message = "^refactor", group = "<!-- 5 -->Refactor" },
{ message = "^test", group = "<!-- 6 -->Testing" },
{ message = "^build", group = "<!-- 7 -->Build System and CI" },
{ message = "^ci", group = "<!-- 7 -->Build System and CI" },
{ message = ".*", group = "<!-- 8 -->Other" },
{ group = "<!-- 0 -->Breaking", message = "!:" },
{ group = "<!-- 1 -->Features", message = "^feat" },
{ group = "<!-- 2 -->Bug Fixes", message = "^fix" },
{ group = "<!-- 3 -->Performance", message = "^perf" },
{ group = "<!-- 4 -->Documentation", message = "^doc" },
{ group = "<!-- 5 -->Refactor", message = "^refactor" },
{ group = "<!-- 6 -->Testing", message = "^test" },
{ group = "<!-- 7 -->Build System and CI", message = "^build" },
{ group = "<!-- 7 -->Build System and CI", message = "^ci" },
{ group = "<!-- 8 -->Other", message = ".*" },
]
# filter out the commits that are not matched by commit parsers
filter_commits = false

View file

@ -4,6 +4,8 @@ updates:
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 3
commit-message:
prefix: "build(deps)"
labels:
@ -12,10 +14,16 @@ updates:
groups:
cargo:
patterns: ["*"]
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major", "version-update:semver-minor"]
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 3
commit-message:
prefix: "ci"
labels:
@ -24,3 +32,22 @@ updates:
groups:
actions:
patterns: ["*"]
- package-ecosystem: "npm"
versioning-strategy: increase
directories:
- "/crates/npm"
- "/crates/eslint"
- "/lib/binding_web"
schedule:
interval: "weekly"
cooldown:
default-days: 3
commit-message:
prefix: "build(deps)"
labels:
- "dependencies"
- "npm"
groups:
npm:
patterns: ["*"]

6
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,6 @@
### AI Policy
- [ ] I have read the [AI Policy](https://tree-sitter.github.io/tree-sitter/6-contributing.html#ai-policy) and this PR complies with it.
- [ ] If AI tools were used: I have disclosed the tool and extent of usage below.
<!-- If you used AI tools, state which tool and how it was used. Delete this section if not applicable. -->

29
.github/scripts/close_spam.js vendored Normal file
View file

@ -0,0 +1,29 @@
module.exports = async ({ github, context }) => {
let target = context.payload.issue;
if (target) {
await github.rest.issues.update({
...context.repo,
issue_number: target.number,
state: "closed",
state_reason: "not_planned",
title: "[spam]",
body: "",
type: null,
});
} else {
target = context.payload.pull_request;
await github.rest.pulls.update({
...context.repo,
pull_number: target.number,
state: "closed",
title: "[spam]",
body: "",
});
}
await github.rest.issues.lock({
...context.repo,
issue_number: target.number,
lock_reason: "spam",
});
};

View file

@ -1,3 +0,0 @@
#!/bin/bash -eu
exec docker run --rm -v /home/runner:/home/runner -w "$PWD" "$CROSS_IMAGE" "$@"

View file

@ -1,9 +0,0 @@
#!/bin/bash -eu
tree_sitter="$ROOT"/target/"$TARGET"/release/tree-sitter
if [[ $BUILD_CMD == cross ]]; then
cross.sh make CC="$CC" AR="$AR" "$@"
else
exec make "$@"
fi

View file

@ -1,16 +0,0 @@
module.exports = async ({ github, context }) => {
const requestedReviewers = await github.rest.pulls.listRequestedReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
});
const reviewers = requestedReviewers.data.users.map((e) => e.login);
github.rest.pulls.removeRequestedReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
reviewers: reviewers,
});
};

View file

@ -1,9 +0,0 @@
#!/bin/bash -eu
tree_sitter="$ROOT"/target/"$TARGET"/release/tree-sitter
if [[ $BUILD_CMD == cross ]]; then
cross.sh "$CROSS_RUNNER" "$tree_sitter" "$@"
else
exec "$tree_sitter" "$@"
fi

35
.github/scripts/wasm_stdlib.js vendored Normal file
View file

@ -0,0 +1,35 @@
module.exports = async ({ github, context, core }) => {
if (context.eventName !== 'pull_request') return;
const prNumber = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
const files = await github.paginate(github.rest.pulls.listFiles, {
owner,
repo,
pull_number: prNumber,
per_page: 100
});
const changedFiles = files.map(file => file.filename);
const wasmStdLibSources = [
'lib/src/wasm-stdlib/external_scanner_allocator.c',
'lib/src/wasm-stdlib/imports.txt',
'lib/src/wasm-stdlib/libc.c',
'lib/src/wasm-stdlib/stdio.c'
];
const dirChanged = changedFiles.some(file =>
wasmStdLibSources.includes(file) ||
file.startsWith('lib/src/wasm-stdlib/libc/ctype/') ||
file.startsWith('lib/src/wasm-stdlib/libc/string/')
);
if (!dirChanged) return;
const wasmStdLibHeader = 'lib/src/wasm-stdlib/external_scanner_stdlib.h';
const requiredChanged = changedFiles.includes(wasmStdLibHeader);
if (!requiredChanged) core.setFailed(`Changes detected in the Wasm stdlib sources but ${wasmStdLibHeader} was not modified.`);
};

View file

@ -14,17 +14,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7.0.1
with:
persist-credentials: true
ref: ${{ github.event.pull_request.base.ref }}
- name: Create app token
uses: actions/create-github-app-token@v1
uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ vars.BACKPORT_APP }}
private-key: ${{ secrets.BACKPORT_KEY }}
- name: Create backport PR
uses: korthout/backport-action@v3
uses: korthout/backport-action@v4.6.0
with:
pull_title: "${pull_title}"
label_pattern: "^ci:backport ([^ ]+)$"

View file

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7.0.1
- name: Set up stable Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1

View file

@ -1,10 +1,5 @@
name: Build & Test
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-D warnings"
CROSS_DEBUG: 1
on:
workflow_call:
inputs:
@ -31,38 +26,46 @@ jobs:
- windows-x86
- macos-arm64
- macos-x64
- illumos-x64
- wasm32
include:
# When adding a new `target`:
# 1. Define a new platform alias above
# 2. Add a new record to the matrix map in `cli/npm/install.js`
- { platform: linux-arm64 , target: aarch64-unknown-linux-gnu , os: ubuntu-latest , use-cross: true }
- { platform: linux-arm , target: arm-unknown-linux-gnueabi , os: ubuntu-latest , use-cross: true }
- { platform: linux-x64 , target: x86_64-unknown-linux-gnu , os: ubuntu-22.04 , features: wasm }
- { platform: linux-x86 , target: i686-unknown-linux-gnu , os: ubuntu-latest , use-cross: true }
- { platform: linux-powerpc64 , target: powerpc64-unknown-linux-gnu , os: ubuntu-latest , use-cross: true }
- { platform: windows-arm64 , target: aarch64-pc-windows-msvc , os: windows-latest }
- { platform: windows-x64 , target: x86_64-pc-windows-msvc , os: windows-latest , features: wasm }
- { platform: windows-x86 , target: i686-pc-windows-msvc , os: windows-latest }
- { platform: macos-arm64 , target: aarch64-apple-darwin , os: macos-latest , features: wasm }
- { platform: macos-x64 , target: x86_64-apple-darwin , os: macos-13 , features: wasm }
# 2. Add a new record to the matrix map in `crates/cli/npm/install.js`
# 3. Consider adding the mapping at the end of 'crates/cli/Cargo.toml' for cargo-binstall support
- { platform: linux-arm64 , target: aarch64-unknown-linux-gnu , os: ubuntu-24.04-arm }
- { platform: linux-arm , target: armv7-unknown-linux-gnueabihf , os: ubuntu-24.04-arm }
- { platform: linux-x64 , target: x86_64-unknown-linux-gnu , os: ubuntu-24.04 }
- { platform: linux-x86 , target: i686-unknown-linux-gnu , os: ubuntu-24.04 }
- { platform: linux-powerpc64 , target: powerpc64-unknown-linux-gnu , os: ubuntu-24.04 }
- { platform: windows-arm64 , target: aarch64-pc-windows-msvc , os: windows-11-arm }
- { platform: windows-x64 , target: x86_64-pc-windows-msvc , os: windows-2025 }
- { platform: windows-x86 , target: i686-pc-windows-msvc , os: windows-2025 }
- { platform: macos-arm64 , target: aarch64-apple-darwin , os: macos-15 }
- { platform: macos-x64 , target: x86_64-apple-darwin , os: macos-15-intel }
- { platform: wasm32 , target: wasm32-unknown-unknown , os: ubuntu-24.04 }
# Cross compilers for C library
- { platform: linux-arm64 , cc: aarch64-linux-gnu-gcc , ar: aarch64-linux-gnu-ar }
- { platform: linux-arm , cc: arm-linux-gnueabi-gcc , ar: arm-linux-gnueabi-ar }
- { platform: linux-x86 , cc: i686-linux-gnu-gcc , ar: i686-linux-gnu-ar }
- { platform: linux-powerpc64 , cc: powerpc64-linux-gnu-gcc , ar: powerpc64-linux-gnu-ar }
# illumos is not supported OOTB, it runs in a vm
- { platform: illumos-x64 , target: x86_64-unknown-illumos , os: ubuntu-24.04 , vm: true , no-run: true }
# Prevent race condition (see #2041)
- { platform: windows-x64 , rust-test-threads: 1 }
- { platform: windows-x86 , rust-test-threads: 1 }
# Extra features
- { platform: linux-arm64 , features: wasm }
- { platform: linux-x64 , features: wasm , run-wasm-test: true }
- { platform: macos-arm64 , features: wasm , run-wasm-test: true }
- { platform: macos-x64 , features: wasm }
# Can't natively run CLI on Github runner's host
- { platform: windows-arm64 , no-run: true }
# Cross-compilation
- { platform: linux-arm , cross: true }
- { platform: linux-x86 , cross: true }
- { platform: linux-powerpc64 , cross: true }
# Compile-only
- { platform: wasm32 , no-run: true }
env:
BUILD_CMD: cargo
SUFFIX: ${{ contains(matrix.target, 'windows') && '.exe' || '' }}
CARGO_TERM_COLOR: always
RUSTFLAGS: -D warnings
defaults:
run:
@ -70,122 +73,186 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7.0.1
- name: Read Emscripten version
run: printf 'EMSCRIPTEN_VERSION=%s\n' "$(<cli/loader/emscripten-version)" >> $GITHUB_ENV
- name: Set up cross-compilation
if: matrix.cross
run: |
for target in armv7-unknown-linux-gnueabihf i686-unknown-linux-gnu powerpc64-unknown-linux-gnu; do
camel_target=${target//-/_}; target_cc=${target/-unknown/}
printf 'CC_%s=%s\n' "$camel_target" "${target_cc/v7/}-gcc"
printf 'AR_%s=%s\n' "$camel_target" "${target_cc/v7/}-ar"
printf 'CARGO_TARGET_%s_LINKER=%s\n' "${camel_target^^}" "${target_cc/v7/}-gcc"
done >> $GITHUB_ENV
{
printf 'CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_RUNNER=qemu-arm -L /usr/arm-linux-gnueabihf\n'
printf 'CARGO_TARGET_POWERPC64_UNKNOWN_LINUX_GNU_RUNNER=qemu-ppc64 -L /usr/powerpc64-linux-gnu\n'
} >> $GITHUB_ENV
- name: Get emscripten version
if: contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test)
run: printf 'EMSCRIPTEN_VERSION=%s\n' "$(<crates/loader/emscripten-version)" >> $GITHUB_ENV
- name: Cache Emscripten SDK
if: contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test)
uses: actions/cache@v6
with:
path: emsdk
key: emsdk-${{ env.EMSCRIPTEN_VERSION }}-${{ runner.os }}-${{ runner.arch }}
- name: Install Emscripten
if: ${{ !matrix.no-run && !matrix.use-cross }}
uses: mymindstorm/setup-emsdk@v14
if: contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test)
run: |
if [[ ! -d emsdk ]]; then
git clone --depth 1 https://github.com/emscripten-core/emsdk.git
fi
cd emsdk
./emsdk install ${{ env.EMSCRIPTEN_VERSION }}
./emsdk activate ${{ env.EMSCRIPTEN_VERSION }}
echo "$PWD" >> "$GITHUB_PATH"
echo "$PWD/upstream/emscripten" >> "$GITHUB_PATH"
echo "EMSDK=$PWD" >> "$GITHUB_ENV"
echo "EMSDK_NODE=$PWD/node/$(ls node)/bin/node" >> "$GITHUB_ENV"
- name: Set up Node.js
if: contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test)
uses: actions/setup-node@v7.0.0
with:
version: ${{ env.EMSCRIPTEN_VERSION }}
node-version: 24
cache: npm
cache-dependency-path: lib/binding_web/package-lock.json
- name: Set up Rust
if: ${{ !matrix.vm }}
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: ${{ matrix.target }}
- name: Install cross
if: ${{ matrix.use-cross }}
- name: Install Rust Wasm test target
if: matrix.run-wasm-test
run: rustup toolchain install nightly --profile minimal --component rust-src
- name: Install cross-compilation toolchain
if: matrix.cross
run: |
if [ ! -x "$(command -v cross)" ]; then
# TODO: Remove 'RUSTFLAGS=""' once https://github.com/cross-rs/cross/issues/1561 is resolved
RUSTFLAGS="" cargo install cross --git https://github.com/cross-rs/cross
sudo apt-get update -qy
if [[ $PLATFORM == linux-arm ]]; then
sudo apt-get install -qy {binutils,gcc}-arm-linux-gnueabihf qemu-user
elif [[ $PLATFORM == linux-x86 ]]; then
sudo apt-get install -qy {binutils,gcc}-i686-linux-gnu
elif [[ $PLATFORM == linux-powerpc64 ]]; then
sudo apt-get install -qy {binutils,gcc}-powerpc64-linux-gnu qemu-user
fi
- name: Configure cross
if: ${{ matrix.use-cross }}
run: |
printf '%s\n' > Cross.toml \
'[target.${{ matrix.target }}]' \
'image = "ghcr.io/cross-rs/${{ matrix.target }}:edge"' \
'[build]' \
'pre-build = [' \
' "dpkg --add-architecture $CROSS_DEB_ARCH",' \
' "curl -fsSL https://deb.nodesource.com/setup_22.x | bash -",' \
' "apt-get update && apt-get -y install libssl-dev nodejs"' \
']'
cat - Cross.toml <<< 'Cross.toml:'
printf '%s\n' >> $GITHUB_ENV \
"CROSS_CONFIG=$PWD/Cross.toml" \
"CROSS_IMAGE=ghcr.io/cross-rs/${{ matrix.target }}:edge"
- name: Set up environment
env:
RUST_TEST_THREADS: ${{ matrix.rust-test-threads }}
USE_CROSS: ${{ matrix.use-cross }}
TARGET: ${{ matrix.target }}
CC: ${{ matrix.cc }}
AR: ${{ matrix.ar }}
PLATFORM: ${{ matrix.platform }}
- name: Install MinGW and Clang (Windows x64 MSYS2)
if: matrix.platform == 'windows-x64'
uses: msys2/setup-msys2@v2
with:
install: |
mingw-w64-x86_64-toolchain
mingw-w64-x86_64-clang
mingw-w64-x86_64-make
mingw-w64-x86_64-cmake
- name: Build C library (Windows x64 MSYS2 CMake)
if: matrix.platform == 'windows-x64'
shell: msys2 {0}
run: |
PATH="$PWD/.github/scripts:$PATH"
printf '%s/.github/scripts\n' "$PWD" >> $GITHUB_PATH
cmake -G Ninja -S . -B build/static \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_COMPILE_WARNING_AS_ERROR=ON \
-DTREE_SITTER_FEATURE_WASM=$WASM \
-DCMAKE_C_COMPILER=clang
cmake --build build/static
printf '%s\n' >> $GITHUB_ENV \
'TREE_SITTER=tree-sitter.sh' \
"TARGET=$TARGET" \
"ROOT=$PWD"
cmake -G Ninja -S . -B build/shared \
-DBUILD_SHARED_LIBS=ON \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_COMPILE_WARNING_AS_ERROR=ON \
-DTREE_SITTER_FEATURE_WASM=$WASM \
-DCMAKE_C_COMPILER=clang
cmake --build build/shared
rm -rf \
build/{static,shared} \
"${CMAKE_PREFIX_PATH}/artifacts" \
target/wasmtime-${WASMTIME_VERSION}
env:
WASM: ${{ contains(matrix.features, 'wasm') && 'ON' || 'OFF' }}
[[ -n $RUST_TEST_THREADS ]] && \
printf 'RUST_TEST_THREADS=%s\n' "$RUST_TEST_THREADS" >> $GITHUB_ENV
[[ -n $CC ]] && printf 'CC=%s\n' "$CC" >> $GITHUB_ENV
[[ -n $AR ]] && printf 'AR=%s\n' "$AR" >> $GITHUB_ENV
if [[ $USE_CROSS == true ]]; then
printf 'BUILD_CMD=cross\n' >> $GITHUB_ENV
runner=$(cross.sh bash -c "env | sed -n 's/^CARGO_TARGET_.*_RUNNER=//p'")
[[ -n $runner ]] && printf 'CROSS_RUNNER=%s\n' "$runner" >> $GITHUB_ENV
fi
- name: Build wasmtime library
if: ${{ !matrix.use-cross && contains(matrix.features, 'wasm') }}
- name: Download wasmtime C API
if: contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test)
run: |
mkdir -p target
WASMTIME_VERSION=$(cargo metadata --format-version=1 --locked --features wasm | \
jq -r '.packages[] | select(.name == "wasmtime-c-api-impl") | .version')
curl -LSs "$WASMTIME_REPO/archive/refs/tags/v${WASMTIME_VERSION}.tar.gz" | tar xzf - -C target
cd target/wasmtime-${WASMTIME_VERSION}
cmake -S crates/c-api -B target/c-api \
-DCMAKE_INSTALL_PREFIX="$PWD/artifacts" \
-DWASMTIME_DISABLE_ALL_FEATURES=ON \
-DWASMTIME_FEATURE_CRANELIFT=ON \
-DWASMTIME_TARGET='${{ matrix.target }}'
cmake --build target/c-api && cmake --install target/c-api
printf 'CMAKE_PREFIX_PATH=%s\n' "$PWD/artifacts" >> $GITHUB_ENV
case '${{ matrix.target }}' in
x86_64-unknown-linux-gnu) WT_TARGET=x86_64-linux ;;
aarch64-unknown-linux-gnu) WT_TARGET=aarch64-linux ;;
x86_64-apple-darwin) WT_TARGET=x86_64-macos ;;
aarch64-apple-darwin) WT_TARGET=aarch64-macos ;;
esac
curl -LSs "$WASMTIME_REPO/releases/download/v${WASMTIME_VERSION}/wasmtime-v${WASMTIME_VERSION}-${WT_TARGET}-c-api.tar.xz" \
| tar xJf - -C target
printf 'CMAKE_PREFIX_PATH=%s\n' "$PWD/target/wasmtime-v${WASMTIME_VERSION}-${WT_TARGET}-c-api" >> $GITHUB_ENV
env:
WASMTIME_REPO: https://github.com/bytecodealliance/wasmtime
- name: Build C library (make)
if: ${{ runner.os != 'Windows' }}
run: make.sh -j CFLAGS="$CFLAGS"
if: runner.os != 'Windows' && !matrix.vm
run: |
if [[ $PLATFORM == linux-arm ]]; then
CC=arm-linux-gnueabihf-gcc; AR=arm-linux-gnueabihf-ar
elif [[ $PLATFORM == linux-x86 ]]; then
CC=i686-linux-gnu-gcc; AR=i686-linux-gnu-ar
elif [[ $PLATFORM == linux-powerpc64 ]]; then
CC=powerpc64-linux-gnu-gcc; AR=powerpc64-linux-gnu-ar
else
CC=gcc; AR=ar
fi
make -j CFLAGS="$CFLAGS" CC=$CC AR=$AR
env:
CFLAGS: -g -Werror -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types
PLATFORM: ${{ matrix.platform }}
CFLAGS: -g -Werror -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types -Werror=strict-aliasing -Wstrict-aliasing=2
- name: Build C library (CMake)
if: ${{ !matrix.use-cross }}
if: "!matrix.cross && !matrix.vm"
run: |
cmake -S lib -B build/static \
cmake -S . -B build/static \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_COMPILE_WARNING_AS_ERROR=ON \
-DTREE_SITTER_FEATURE_WASM=$WASM
cmake --build build/static --verbose
cmake -S lib -B build/shared \
cmake -S . -B build/shared \
-DBUILD_SHARED_LIBS=ON \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_COMPILE_WARNING_AS_ERROR=ON \
-DTREE_SITTER_FEATURE_WASM=$WASM
cmake --build build/shared --verbose
env:
CC: ${{ contains(matrix.target, 'linux') && 'clang' || '' }}
WASM: ${{ contains(matrix.features, 'wasm') && 'ON' || 'OFF' }}
CC: ${{ contains(matrix.platform, 'linux') && 'clang' || '' }}
WASM: ${{ contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test) && 'ON' || 'OFF' }}
- name: Build wasm library
# No reason to build on the same Github runner hosts many times
if: ${{ !matrix.no-run && !matrix.use-cross }}
- name: Build C library and Rust crate (illumos gmake)
if: matrix.platform == 'illumos-x64'
uses: vmactions/omnios-vm@v1.3.6
with:
release: r151056-build
copyback: false
prepare: |
pkg install -q build-essential || [ $? -eq 4 ]
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
run: |
. "$HOME/.cargo/env"
gmake -j
cargo build -p tree-sitter
- name: Build Wasm library
if: contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test)
shell: bash
run: |
cd lib/binding_web
@ -195,64 +262,77 @@ jobs:
npm run build
npm run build:debug
- name: Check no_std builds
if: inputs.run-test && !matrix.no-run
working-directory: lib
shell: bash
run: cargo check --no-default-features --target='${{ matrix.target }}'
- name: Build target
run: $BUILD_CMD build --release --target=${{ matrix.target }} --features=${{ matrix.features }}
if: "!inputs.run-test && !matrix.vm"
run: cargo build --release --target='${{ matrix.target }}' --features='${{ (matrix.run-wasm-test || !inputs.run-test) && matrix.features || '' }}' $PACKAGE
env:
PACKAGE: ${{ matrix.platform == 'wasm32' && '-p tree-sitter' || '' }}
- name: Cache fixtures
id: cache
if: ${{ !matrix.no-run && inputs.run-test }}
if: inputs.run-test && !matrix.no-run
uses: ./.github/actions/cache
- name: Fetch fixtures
if: ${{ !matrix.no-run && inputs.run-test }}
run: $BUILD_CMD run -p xtask -- fetch-fixtures
if: inputs.run-test && !matrix.no-run
run: cargo run -p xtask --target='${{ matrix.target }}' -- fetch-fixtures
- name: Generate fixtures
if: ${{ !matrix.no-run && inputs.run-test && steps.cache.outputs.cache-hit != 'true' }}
run: $BUILD_CMD run -p xtask -- generate-fixtures
if: inputs.run-test && !matrix.no-run && steps.cache.outputs.cache-hit != 'true'
run: cargo run -p xtask --target='${{ matrix.target }}' -- generate-fixtures
- name: Generate Wasm fixtures
if: ${{ !matrix.no-run && !matrix.use-cross && inputs.run-test && steps.cache.outputs.cache-hit != 'true' }}
run: $BUILD_CMD run -p xtask -- generate-fixtures --wasm
if: inputs.run-test && !matrix.no-run && contains(matrix.features, 'wasm') && matrix.run-wasm-test && steps.cache.outputs.cache-hit != 'true'
run: cargo run -p xtask --target='${{ matrix.target }}' -- generate-fixtures --wasm
- name: Run main tests
if: ${{ !matrix.no-run && inputs.run-test }}
run: $BUILD_CMD test --target=${{ matrix.target }} --features=${{ matrix.features }}
if: inputs.run-test && !matrix.no-run
run: cargo test --workspace --target='${{ matrix.target }}' --features='${{ (matrix.run-wasm-test || !inputs.run-test) && matrix.features || '' }}'
- name: Run wasm tests
if: ${{ !matrix.no-run && !matrix.use-cross && inputs.run-test }}
run: $BUILD_CMD run -p xtask -- test-wasm
- name: Run Wasm tests
if: inputs.run-test && !matrix.no-run && contains(matrix.features, 'wasm') && matrix.run-wasm-test
run: cargo run -p xtask --target='${{ matrix.target }}' -- test-wasm
- name: Run benchmarks
# Cross-compiled benchmarks are pointless
if: ${{ !matrix.no-run && !matrix.use-cross && inputs.run-test }}
run: $BUILD_CMD bench benchmark -p tree-sitter-cli --target=${{ matrix.target }}
- name: Run Rust Wasm web test
if: inputs.run-test && !matrix.no-run && contains(matrix.features, 'wasm') && matrix.run-wasm-test
run: cargo run -p xtask --target='${{ matrix.target }}' -- test-rust-wasm-web
- name: Upload CLI artifact
uses: actions/upload-artifact@v4
if: "!inputs.run-test && !matrix.no-run"
uses: actions/upload-artifact@v7
with:
name: tree-sitter.${{ matrix.platform }}
path: target/${{ matrix.target }}/release/tree-sitter${{ env.SUFFIX }}
path: target/${{ matrix.target }}/release/tree-sitter${{ contains(matrix.target, 'windows') && '.exe' || '' }}
if-no-files-found: error
retention-days: 7
- name: Upload Wasm artifacts
if: ${{ matrix.platform == 'linux-x64' }}
uses: actions/upload-artifact@v4
if: "!inputs.run-test && matrix.platform == 'linux-x64'"
uses: actions/upload-artifact@v7
with:
name: tree-sitter.wasm
path: |
lib/binding_web/tree-sitter.js
lib/binding_web/tree-sitter.js.map
lib/binding_web/tree-sitter.cjs
lib/binding_web/tree-sitter.cjs.map
lib/binding_web/tree-sitter.wasm
lib/binding_web/tree-sitter.wasm.map
lib/binding_web/debug/tree-sitter.cjs
lib/binding_web/debug/tree-sitter.cjs.map
lib/binding_web/debug/tree-sitter.js
lib/binding_web/debug/tree-sitter.js.map
lib/binding_web/debug/tree-sitter.wasm
lib/binding_web/debug/tree-sitter.wasm.map
lib/binding_web/web-tree-sitter.js
lib/binding_web/web-tree-sitter.js.map
lib/binding_web/web-tree-sitter.cjs
lib/binding_web/web-tree-sitter.cjs.map
lib/binding_web/web-tree-sitter.wasm
lib/binding_web/web-tree-sitter.wasm.map
lib/binding_web/debug/web-tree-sitter.cjs
lib/binding_web/debug/web-tree-sitter.cjs.map
lib/binding_web/debug/web-tree-sitter.js
lib/binding_web/debug/web-tree-sitter.js.map
lib/binding_web/debug/web-tree-sitter.wasm
lib/binding_web/debug/web-tree-sitter.wasm.map
lib/binding_web/lib/*.c
lib/binding_web/lib/*.h
lib/binding_web/lib/*.ts
lib/binding_web/src/*.ts
if-no-files-found: error
retention-days: 7

View file

@ -1,21 +1,14 @@
name: CI
on:
pull_request:
paths-ignore:
- docs/**
- "**/README.md"
- CONTRIBUTING.md
- LICENSE
- cli/src/templates
push:
branches: [master]
paths-ignore:
- docs/**
- "**/README.md"
- CONTRIBUTING.md
- LICENSE
- cli/src/templates
branches:
- 'master'
- 'release-[0-9]+.[0-9]+'
pull_request:
branches:
- 'master'
- 'release-[0-9]+.[0-9]+'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@ -26,26 +19,33 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7.0.1
- name: Set up stable Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
- name: Set up nightly Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: nightly
components: clippy, rustfmt
- name: Lint files
run: |
make lint
make lint-web
- name: Lint Rust files
run: make lint
- name: Install Taplo
uses: taiki-e/install-action@v2
with:
tool: taplo@0.10.0
- name: Lint TOML files
run: make lint-toml
- name: Lint web files
run: make lint-web
sanitize:
uses: ./.github/workflows/sanitize.yml
build:
uses: ./.github/workflows/build.yml
check-wasm-stdlib:
uses: ./.github/workflows/wasm_stdlib.yml

22
.github/workflows/crate_versions.yml vendored Normal file
View file

@ -0,0 +1,22 @@
name: Crate Versions Check
on:
pull_request:
types: [labeled, opened, synchronize, reopened]
workflow_dispatch:
jobs:
check-crates:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'ci:check release') || github.event_name == 'workflow_dispatch'
steps:
- name: Checkout repository
uses: actions/checkout@v7.0.1
- name: Set up Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Check crates against crates.io
uses: katyo/publish-crates@v2
with:
dry-run: true

View file

@ -3,6 +3,7 @@ on:
push:
branches: [master]
paths: [docs/**]
workflow_dispatch:
jobs:
deploy-docs:
@ -15,35 +16,29 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
uses: actions/checkout@v7.0.1
- name: Install mdbook
env:
GH_TOKEN: ${{ github.token }}
run: |
jq_expr='.assets[] | select(.name | contains("x86_64-unknown-linux-gnu")) | .browser_download_url'
url=$(gh api repos/rust-lang/mdbook/releases/latest --jq "$jq_expr")
url=$(gh api repos/rust-lang/mdbook/releases/tags/v0.5.4 --jq "$jq_expr")
mkdir mdbook
curl -sSL "$url" | tar -xz -C mdbook
printf '%s/mdbook\n' "$PWD" >> "$GITHUB_PATH"
- name: Install mdbook-admonish
run: cargo install mdbook-admonish
- name: Build Book
run: mdbook build docs
- name: Setup Pages
uses: actions/configure-pages@v5
uses: actions/configure-pages@v6
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v5
with:
path: docs/book
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v5

View file

@ -1,30 +0,0 @@
name: Update Emscripten
on:
pull_request:
types: [opened, synchronize]
permissions:
contents: write
pull-requests: read
jobs:
update-emscripten:
if: github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Set up stable Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Run emscripten update xtask
run: |
git config --global user.name "dependabot[bot]"
git config --global user.email "49699333+dependabot[bot]@users.noreply.github.com"
cargo xtask upgrade-emscripten
- name: Push updated version
run: git push origin HEAD:$GITHUB_HEAD_REF

View file

@ -3,7 +3,11 @@ name: nvim-treesitter parser tests
on:
pull_request:
paths:
- 'cli/**'
- 'lib/**'
- 'crates/cli/**'
- 'crates/config/**'
- 'crates/generate/**'
- 'crates/loader/**'
- '.github/workflows/nvim_ts.yml'
workflow_dispatch:
@ -13,7 +17,7 @@ concurrency:
jobs:
check_compilation:
timeout-minutes: 20
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
@ -22,45 +26,71 @@ jobs:
name: ${{ matrix.os }} - ${{ matrix.type }}
runs-on: ${{ matrix.os }}
env:
NVIM: ${{ matrix.os == 'windows-latest' && 'nvim-win64\\bin\\nvim.exe' || 'nvim' }}
NVIM: ${{ matrix.os == 'windows-latest' && 'nvim.exe' || 'nvim' }}
NVIM_TAG: stable
NVIM_DIR: neovim
NVIM_TS_DIR: nvim-treesitter
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7.0.1
- uses: actions-rust-lang/setup-rust-toolchain@v1
- run: cargo build --profile optimize
- uses: actions/checkout@v4
- name: Clone Neovim
uses: actions/checkout@v7.0.1
with:
repository: neovim/neovim
ref: ${{ env.NVIM_TAG }}
path: ${{ env.NVIM_DIR }}
- if: runner.os != 'Windows'
name: Setup environment (Posix)
run: |
echo ${{ github.workspace }}/target/optimize >> "$GITHUB_PATH"
echo ${{ github.workspace }}/neovim/build/bin >> "$GITHUB_PATH"
echo "VIMRUNTIME=${{ github.workspace }}/neovim/runtime" >> "$GITHUB_ENV"
- if: runner.os == 'Windows'
name: Setup environment (why can't you just be normal?!)
run: |
${{ env.NVIM_DIR }}/.github/scripts/env.ps1
echo ${{ github.workspace }}/target/optimize >> "$env:GITHUB_PATH"
echo ${{ github.workspace }}/neovim/build/bin >> "$env:GITHUB_PATH"
echo "VIMRUNTIME=${{ github.workspace }}/neovim/runtime" >> "$env:GITHUB_ENV"
- name: Build Neovim
working-directory: ${{ env.NVIM_DIR }}
run: |
cmake -S cmake.deps -B .deps -G Ninja -D CMAKE_BUILD_TYPE=Release -D TREESITTER_URL=https://github.com/tree-sitter/tree-sitter/archive/${{ github.event.pull_request.head.sha }}.tar.gz -D DEPS_IGNORE_SHA=TRUE
cmake --build .deps --config Release
cmake -B build -G Ninja -D CMAKE_BUILD_TYPE=Release
cmake --build build --config Release
- name: Clone nvim-treesitter
uses: actions/checkout@v7.0.1
with:
repository: nvim-treesitter/nvim-treesitter
path: ${{ env.NVIM_TS_DIR }}
ref: main
- if: runner.os != 'Windows'
run: echo ${{ github.workspace }}/target/release >> $GITHUB_PATH
- if: runner.os == 'Windows'
run: echo ${{ github.workspace }}/target/release >> $env:GITHUB_PATH
- uses: actions-rust-lang/setup-rust-toolchain@v1
- run: cargo build --release
- uses: ilammy/msvc-dev-cmd@v1
- name: Install and prepare Neovim
run: bash ./scripts/ci-install.sh
working-directory: ${{ env.NVIM_TS_DIR }}
- if: matrix.type == 'generate'
name: Generate and compile parsers
run: $NVIM -l ./scripts/install-parsers.lua --generate --max-jobs=2
run: $NVIM -l ./scripts/install-parsers.lua --generate --max-jobs=10
working-directory: ${{ env.NVIM_TS_DIR }}
shell: bash
- if: matrix.type == 'build'
name: Compile parsers
run: $NVIM -l ./scripts/install-parsers.lua
run: $NVIM -l ./scripts/install-parsers.lua --max-jobs=10
working-directory: ${{ env.NVIM_TS_DIR }}
shell: bash
- if: "!cancelled()"
name: Check query files
name: Test parsers
run: $NVIM -l ./scripts/check-parsers.lua
working-directory: ${{ env.NVIM_TS_DIR }}
shell: bash
- if: "!cancelled()"
name: Test queries
run: $NVIM -l ./scripts/check-queries.lua
working-directory: ${{ env.NVIM_TS_DIR }}
shell: bash

View file

@ -1,7 +1,14 @@
name: Release
on:
schedule:
- cron: '5 5 * * *'
workflow_dispatch:
inputs:
tag_name:
description: 'Tag name for release'
required: false
default: nightly
push:
tags:
- v[0-9]+.[0-9]+.[0-9]+
@ -17,13 +24,26 @@ jobs:
runs-on: ubuntu-latest
needs: build
permissions:
id-token: write
attestations: write
contents: write
steps:
- if: github.event_name == 'workflow_dispatch'
env:
TAG_NAME: ${{ github.event.inputs.tag_name }}
run: echo "TAG_NAME=${TAG_NAME}" >> $GITHUB_ENV
- if: github.event_name == 'schedule'
run: echo 'TAG_NAME=nightly' >> $GITHUB_ENV
- if: github.event_name == 'push'
run: echo "TAG_NAME=${GITHUB_REF_NAME}" >> $GITHUB_ENV
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7.0.1
- name: Download build artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
path: artifacts
@ -33,84 +53,93 @@ jobs:
- name: Prepare release artifacts
run: |
mkdir -p target
mv artifacts/tree-sitter.wasm/* target/
mkdir -p target web
mv artifacts/tree-sitter.wasm/* web/
# Rename files
mv target/tree-sitter.js target/web-tree-sitter.js
mv target/tree-sitter.js.map target/web-tree-sitter.js.map
mv target/tree-sitter.cjs target/web-tree-sitter.cjs
mv target/tree-sitter.cjs.map target/web-tree-sitter.cjs.map
mv target/tree-sitter.wasm target/web-tree-sitter.wasm
mv target/tree-sitter.wasm.map target/web-tree-sitter.wasm.map
mv target/debug/tree-sitter.js target/web-tree-sitter-debug.js
mv target/debug/tree-sitter.js.map target/web-tree-sitter-debug.js.map
mv target/debug/tree-sitter.cjs target/web-tree-sitter-debug.cjs
mv target/debug/tree-sitter.cjs.map target/web-tree-sitter-debug.cjs.map
mv target/debug/tree-sitter.wasm target/web-tree-sitter-debug.wasm
mv target/debug/tree-sitter.wasm.map target/web-tree-sitter-debug.wasm.map
rm -rf target/debug
tar -czf target/web-tree-sitter.tar.gz -C web .
rm -r artifacts/tree-sitter.wasm
for platform in $(cd artifacts; ls | sed 's/^tree-sitter\.//'); do
exe=$(ls artifacts/tree-sitter.$platform/tree-sitter*)
chmod +x $exe
gzip --stdout --name $exe > target/tree-sitter-$platform.gz
zip -j9 target/tree-sitter-cli-$platform.zip $exe
done
rm -rf artifacts
ls -l target/
- name: Generate attestations
uses: actions/attest-build-provenance@v4
with:
subject-path: |
target/tree-sitter-*.gz
target/tree-sitter-cli-*.zip
target/web-tree-sitter.tar.gz
- if: env.TAG_NAME == 'nightly'
run: |
echo 'PRERELEASE=--prerelease' >> $GITHUB_ENV
gh release delete nightly --yes || true
git push https://${GITHUB_ACTOR}:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY} :nightly || true
env:
GH_TOKEN: ${{ github.token }}
- name: Create release
run: |-
gh release create ${{ github.ref_name }} \
gh release create ${{ env.TAG_NAME }} $PRERELEASE \
target/tree-sitter-*.gz \
target/web-tree-sitter.js \
target/web-tree-sitter.js.map \
target/web-tree-sitter.cjs \
target/web-tree-sitter.cjs.map \
target/web-tree-sitter.wasm \
target/web-tree-sitter.wasm.map \
target/web-tree-sitter-debug.js \
target/web-tree-sitter-debug.js.map \
target/web-tree-sitter-debug.cjs \
target/web-tree-sitter-debug.cjs.map \
target/web-tree-sitter-debug.wasm \
target/web-tree-sitter-debug.wasm.map
target/tree-sitter-cli-*.zip \
target/web-tree-sitter.tar.gz
env:
GH_TOKEN: ${{ github.token }}
crates_io:
name: Publish packages to Crates.io
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly')
runs-on: ubuntu-latest
environment: crates
permissions:
id-token: write
contents: read
needs: release
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7.0.1
- name: Set up Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Set up registry token
id: auth
uses: rust-lang/crates-io-auth-action@v1
- name: Publish crates to Crates.io
uses: katyo/publish-crates@v2
with:
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
registry-token: ${{ steps.auth.outputs.token }}
npm:
name: Publish packages to npmjs.com
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly')
runs-on: ubuntu-latest
environment: npm
permissions:
id-token: write
contents: read
needs: release
strategy:
fail-fast: false
matrix:
directory: [cli/npm, lib/binding_web]
directory: [crates/cli/npm, lib/binding_web]
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7.0.1
- name: Set up Node
uses: actions/setup-node@v4
uses: actions/setup-node@v7.0.0
with:
node-version: 20
node-version: 24
registry-url: https://registry.npmjs.org
- name: Set up Rust
@ -125,9 +154,8 @@ jobs:
npm run build:debug
CJS=true npm run build
CJS=true npm run build:debug
npm run build:dts
- name: Publish to npmjs.com
working-directory: ${{ matrix.directory }}
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View file

@ -17,13 +17,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout script
uses: actions/checkout@v4
uses: actions/checkout@v7.0.1
with:
sparse-checkout: .github/scripts/close_unresponsive.js
sparse-checkout-cone-mode: false
- name: Run script
uses: actions/github-script@v7
uses: actions/github-script@v9
with:
script: |
const script = require('./.github/scripts/close_unresponsive.js')
@ -35,13 +35,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout script
uses: actions/checkout@v4
uses: actions/checkout@v7.0.1
with:
sparse-checkout: .github/scripts/remove_response_label.js
sparse-checkout-cone-mode: false
- name: Run script
uses: actions/github-script@v7
uses: actions/github-script@v9
with:
script: |
const script = require('./.github/scripts/remove_response_label.js')

View file

@ -11,15 +11,21 @@ jobs:
remove-reviewers:
runs-on: ubuntu-latest
steps:
- name: Checkout script
uses: actions/checkout@v4
with:
sparse-checkout: .github/scripts/reviewers_remove.js
sparse-checkout-cone-mode: false
- name: Run script
uses: actions/github-script@v7
- name: Remove reviewers
uses: actions/github-script@v9
with:
script: |
const script = require('./.github/scripts/reviewers_remove.js')
await script({github, context})
const requestedReviewers = await github.rest.pulls.listRequestedReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
});
const reviewers = requestedReviewers.data.users.map((e) => e.login);
github.rest.pulls.removeRequestedReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
reviewers: reviewers,
});

View file

@ -15,7 +15,7 @@ jobs:
TREE_SITTER: ${{ github.workspace }}/target/release/tree-sitter
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7.0.1
- name: Install UBSAN library
run: sudo apt-get update -y && sudo apt-get install -y libubsan1

29
.github/workflows/spam.yml vendored Normal file
View file

@ -0,0 +1,29 @@
name: Close as spam
on:
issues:
types: [labeled]
pull_request_target:
types: [labeled]
permissions:
issues: write
pull-requests: write
jobs:
spam:
runs-on: ubuntu-latest
if: github.event.label.name == 'spam'
steps:
- name: Checkout script
uses: actions/checkout@v7.0.1
with:
sparse-checkout: .github/scripts/close_spam.js
sparse-checkout-cone-mode: false
- name: Run script
uses: actions/github-script@v9
with:
script: |
const script = require('./.github/scripts/close_spam.js')
await script({github, context})

View file

@ -1,23 +1,24 @@
name: Check WASM Exports
name: Check Wasm Exports
on:
pull_request:
paths:
- lib/include/tree_sitter/api.h
- lib/binding_web/**
- xtask/src/**
push:
branches: [master]
paths:
- lib/include/tree_sitter/api.h
- lib/binding_rust/bindings.rs
- lib/CMakeLists.txt
- CMakeLists.txt
jobs:
check-wasm-exports:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7.0.1
- name: Set up stable Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
@ -30,11 +31,11 @@ jobs:
- name: Build C library (make)
run: make -j CFLAGS="$CFLAGS"
env:
CFLAGS: -g -Werror -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types
CFLAGS: -g -Werror -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types -Werror=strict-aliasing -Wstrict-aliasing=2
- name: Build WASM Library
- name: Build Wasm Library
working-directory: lib/binding_web
run: npm ci && npm run build:debug
- name: Check WASM exports
- name: Check Wasm exports
run: cargo xtask check-wasm-exports

19
.github/workflows/wasm_stdlib.yml vendored Normal file
View file

@ -0,0 +1,19 @@
name: Check Wasm Stdlib build
on:
workflow_call:
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7.0.1
- name: Check directory changes
uses: actions/github-script@v9
with:
script: |
const scriptPath = `${process.env.GITHUB_WORKSPACE}/.github/scripts/wasm_stdlib.js`;
const script = require(scriptPath);
return script({ github, context, core });

5
.gitignore vendored
View file

@ -1,10 +1,12 @@
log*.html
.direnv
.idea
*.xcodeproj
.vscode
.cache
.zig-cache
.direnv
profile*
fuzz-results
@ -24,6 +26,7 @@ docs/assets/js/tree-sitter.js
*.dylib
*.so
*.so.[0-9]*
*.dll
*.o
*.obj
*.exp
@ -33,3 +36,5 @@ docs/assets/js/tree-sitter.js
.build
build
zig-*
/result

19
.taplo.toml Normal file
View file

@ -0,0 +1,19 @@
[formatting]
column_width = 100
compact_arrays = false
reorder_inline_tables = true
reorder_keys = true
[[rule]]
include = [ "**/Cargo.toml" ]
keys = [ "package" ]
[rule.formatting]
reorder_keys = false
[[rule]]
include = [ "**/Cargo.toml" ]
keys = [ "profile" ]
[rule.formatting]
reorder_keys = false

View file

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.13)
project(tree-sitter
VERSION "0.25.1"
VERSION "0.28.0"
DESCRIPTION "An incremental parsing system for programming tools"
HOMEPAGE_URL "https://tree-sitter.github.io/tree-sitter/"
LANGUAGES C)
@ -11,15 +11,15 @@ option(TREE_SITTER_FEATURE_WASM "Enable the Wasm feature" OFF)
option(AMALGAMATED "Build using an amalgamated source" OFF)
if(AMALGAMATED)
set(TS_SOURCE_FILES "${PROJECT_SOURCE_DIR}/src/lib.c")
set(TS_SOURCE_FILES "${PROJECT_SOURCE_DIR}/lib/src/lib.c")
else()
file(GLOB TS_SOURCE_FILES src/*.c)
list(REMOVE_ITEM TS_SOURCE_FILES "${PROJECT_SOURCE_DIR}/src/lib.c")
file(GLOB TS_SOURCE_FILES lib/src/*.c)
list(REMOVE_ITEM TS_SOURCE_FILES "${PROJECT_SOURCE_DIR}/lib/src/lib.c")
endif()
add_library(tree-sitter ${TS_SOURCE_FILES})
target_include_directories(tree-sitter PRIVATE src src/wasm include)
target_include_directories(tree-sitter PRIVATE lib/src lib/src/wasm PUBLIC lib/include)
if(MSVC)
target_compile_options(tree-sitter PRIVATE
@ -33,7 +33,8 @@ if(MSVC)
else()
target_compile_options(tree-sitter PRIVATE
-Wall -Wextra -Wshadow -Wpedantic
-Werror=incompatible-pointer-types)
-Werror=incompatible-pointer-types
-Werror=strict-aliasing -Wstrict-aliasing=2)
endif()
if(TREE_SITTER_FEATURE_WASM)
@ -81,15 +82,15 @@ set_target_properties(tree-sitter
SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}"
DEFINE_SYMBOL "")
target_compile_definitions(tree-sitter PRIVATE _POSIX_C_SOURCE=200112L _DEFAULT_SOURCE)
configure_file(tree-sitter.pc.in "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter.pc" @ONLY)
target_compile_definitions(tree-sitter PRIVATE _POSIX_C_SOURCE=200112L _DEFAULT_SOURCE _BSD_SOURCE _DARWIN_C_SOURCE)
include(GNUInstallDirs)
install(FILES include/tree_sitter/api.h
configure_file(lib/tree-sitter.pc.in "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter.pc" @ONLY)
install(FILES lib/include/tree_sitter/api.h
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/tree_sitter")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter.pc"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig")
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
install(TARGETS tree-sitter
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}")

BIN
Cargo.lock generated

Binary file not shown.

View file

@ -1,37 +1,35 @@
[workspace]
default-members = ["cli"]
default-members = [ "crates/cli" ]
members = [
"cli",
"cli/config",
"cli/loader",
"crates/cli",
"crates/config",
"crates/generate",
"crates/highlight",
"crates/loader",
"crates/tags",
"crates/xtask",
"crates/language",
"lib",
"lib/language",
"tags",
"highlight",
"xtask",
]
resolver = "2"
[workspace.package]
version = "0.25.1"
authors = [
"Max Brunsfeld <maxbrunsfeld@gmail.com>",
"Amaan Qureshi <amaanq12@gmail.com>",
]
edition = "2021"
rust-version = "1.82"
authors = [ "Max Brunsfeld <maxbrunsfeld@gmail.com>", "Amaan Qureshi <amaanq12@gmail.com>" ]
categories = [ "command-line-utilities", "parsing" ]
edition = "2024"
homepage = "https://tree-sitter.github.io/tree-sitter"
repository = "https://github.com/tree-sitter/tree-sitter"
keywords = [ "incremental", "parsing" ]
license = "MIT"
keywords = ["incremental", "parsing"]
categories = ["command-line-utilities", "parsing"]
repository = "https://github.com/tree-sitter/tree-sitter"
rust-version = "1.90"
version = "0.28.0"
[workspace.lints.clippy]
dbg_macro = "deny"
todo = "deny"
pedantic = { level = "warn", priority = -1 }
nursery = { level = "warn", priority = -1 }
cargo = { level = "warn", priority = -1 }
dbg_macro = "deny"
nursery = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
todo = "deny"
# The lints below are a specific subset of the pedantic+nursery lints
# that we explicitly allow in the tree-sitter codebase because they either:
@ -40,47 +38,31 @@ cargo = { level = "warn", priority = -1 }
# 2. Are unnecessary, or
# 3. Worsen the code
branches_sharing_code = "allow"
cast_lossless = "allow"
cast_possible_truncation = "allow"
cast_possible_wrap = "allow"
cast_precision_loss = "allow"
cast_sign_loss = "allow"
checked_conversions = "allow"
cognitive_complexity = "allow"
collection_is_never_read = "allow"
fallible_impl_from = "allow"
fn_params_excessive_bools = "allow"
inline_always = "allow"
if_not_else = "allow"
items_after_statements = "allow"
match_wildcard_for_single_variants = "allow"
missing_errors_doc = "allow"
missing_panics_doc = "allow"
module_name_repetitions = "allow"
multiple_crate_versions = "allow"
option_if_let_else = "allow"
or_fun_call = "allow"
range_plus_one = "allow"
redundant_clone = "allow"
redundant_closure_for_method_calls = "allow"
ref_option = "allow"
similar_names = "allow"
string_lit_as_bytes = "allow"
struct_excessive_bools = "allow"
struct_field_names = "allow"
transmute_undefined_repr = "allow"
too_many_lines = "allow"
unnecessary_wraps = "allow"
unused_self = "allow"
used_underscore_items = "allow"
tuple_array_conversions = "allow"
[workspace.lints.rust]
mismatched_lifetime_syntaxes = "allow"
[profile.optimize]
inherits = "release"
strip = true # Automatically strip symbols from the binary.
codegen-units = 1 # Maximum size reduction optimizations.
lto = true # Link-time optimization.
opt-level = 3 # Optimization level 3.
codegen-units = 1 # Maximum size reduction optimizations.
strip = true # Automatically strip symbols from the binary.
[profile.size]
inherits = "optimize"
@ -88,70 +70,69 @@ opt-level = "s" # Optimize for size.
[profile.release-dev]
inherits = "release"
lto = false
codegen-units = 256
debug = true
debug-assertions = true
overflow-checks = true
incremental = true
codegen-units = 256
lto = false
overflow-checks = true
[workspace.dependencies]
ansi_colours = "1.2.3"
anstyle = "1.0.10"
anyhow = "1.0.95"
bstr = "1.11.3"
cc = "1.2.10"
clap = { version = "4.5.27", features = [
anstyle = "1.0.14"
anyhow = "1.0.102"
bstr = "1.12.1"
cc = "1.2.63"
clap = { features = [
"cargo",
"derive",
"env",
"help",
"string",
"unstable-styles",
] }
clap_complete = "4.5.42"
clap_complete_nushell = "4.5.5"
ctor = "0.2.9"
ctrlc = { version = "3.4.5", features = ["termination"] }
dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
etcetera = "0.8.0"
filetime = "0.2.25"
fs4 = "0.12.0"
git2 = "0.20.0"
glob = "0.3.2"
], version = "4.5.58" }
clap_complete = "4.6.3"
clap_complete_nushell = "4.5.10"
crc32fast = "1.5.0"
ctor = "0.6.3"
ctrlc = { features = [ "termination" ], version = "3.5.2" }
dialoguer = { features = [ "fuzzy-select" ], version = "0.12.0" }
etcetera = "0.11.0"
glob = "0.3.3"
hashbrown = { default-features = false, version = "0.17.1" }
heck = "0.5.0"
html-escape = "0.2.13"
indexmap = "2.7.1"
indoc = "2.0.5"
libloading = "0.8.6"
log = { version = "0.4.25", features = ["std"] }
memchr = "2.7.4"
once_cell = "1.20.2"
path-slash = "0.2.1"
indexmap = "2.13.0"
indoc = "2.0.7"
libloading = "0.9.0"
log = { features = [ "std" ], version = "0.4.30" }
memchr = "2.8.1"
once_cell = "1.21.3"
pretty_assertions = "1.4.1"
rand = "0.8.5"
regex = "1.11.1"
regex-syntax = "0.8.5"
rustc-hash = "2.1.0"
semver = { version = "1.0.25", features = ["serde"] }
serde = { version = "1.0.217", features = ["derive"] }
serde_derive = "1.0.217"
serde_json = { version = "1.0.137", features = ["preserve_order"] }
rand = "0.10.1"
regex = "1.12.3"
regex-syntax = "0.8.9"
rustc-hash = "2.1.1"
schemars = "1.2.1"
semver = { features = [ "serde" ], version = "1.0.27" }
serde = { features = [ "derive" ], version = "1.0.228" }
serde_json = { features = [ "preserve_order" ], version = "1.0.150" }
similar = "2.7.0"
smallbitvec = "2.5.3"
streaming-iterator = "0.1.9"
tempfile = "3.15.0"
thiserror = "2.0.11"
tempfile = "3.25.0"
thiserror = "2.0.18"
tiny_http = "0.12.0"
toml = "0.8.19"
unindent = "0.2.3"
url = { version = "2.5.4", features = ["serde"] }
topological-sort = "0.2.2"
unindent = "0.2.4"
walkdir = "2.5.0"
wasmparser = "0.224.0"
webbrowser = "1.0.3"
wasmparser = "0.244.0"
webbrowser = "1.2.1"
tree-sitter = { version = "0.25.1", path = "./lib" }
tree-sitter-generate = { version = "0.25.1", path = "./cli/generate" }
tree-sitter-loader = { version = "0.25.1", path = "./cli/loader" }
tree-sitter-config = { version = "0.25.1", path = "./cli/config" }
tree-sitter-highlight = { version = "0.25.1", path = "./highlight" }
tree-sitter-tags = { version = "0.25.1", path = "./tags" }
tree-sitter = { path = "./lib", version = "0.28.0" }
tree-sitter-config = { path = "./crates/config", version = "0.28.0" }
tree-sitter-generate = { default-features = false, path = "./crates/generate", version = "0.28.0" }
tree-sitter-highlight = { path = "./crates/highlight", version = "0.28.0" }
tree-sitter-loader = { path = "./crates/loader", version = "0.28.0" }
tree-sitter-tags = { path = "./crates/tags", version = "0.28.0" }
tree-sitter-language = { path = "./crates/language", version = "0.1.8" }

View file

@ -1,10 +0,0 @@
FROM rust:1.76-buster
WORKDIR /app
RUN apt-get update
RUN apt-get install -y nodejs
COPY . .
CMD cargo test --all-features

View file

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2018-2024 Max Brunsfeld
Copyright (c) 2018 Max Brunsfeld
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,8 +1,4 @@
ifeq ($(OS),Windows_NT)
$(error Windows is not supported)
endif
VERSION := 0.25.1
VERSION := 0.28.0
DESCRIPTION := An incremental parsing system for programming tools
HOMEPAGE_URL := https://tree-sitter.github.io/tree-sitter/
@ -10,6 +6,7 @@ HOMEPAGE_URL := https://tree-sitter.github.io/tree-sitter/
PREFIX ?= /usr/local
INCLUDEDIR ?= $(PREFIX)/include
LIBDIR ?= $(PREFIX)/lib
BINDIR ?= $(PREFIX)/bin
PCLIBDIR ?= $(LIBDIR)/pkgconfig
# collect sources
@ -25,9 +22,9 @@ OBJ := $(SRC:.c=.o)
# define default flags, and override to append mandatory flags
ARFLAGS := rcs
CFLAGS ?= -O3 -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types
CFLAGS ?= -O3 -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types -Werror=strict-aliasing -Wstrict-aliasing=2
override CFLAGS += -std=c11 -fPIC -fvisibility=hidden
override CFLAGS += -D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE
override CFLAGS += -D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_DARWIN_C_SOURCE
override CFLAGS += -Ilib/src -Ilib/src/wasm -Ilib/include
# ABI versioning
@ -35,20 +32,25 @@ SONAME_MAJOR := $(word 1,$(subst ., ,$(VERSION)))
SONAME_MINOR := $(word 2,$(subst ., ,$(VERSION)))
# OS-specific bits
ifneq ($(findstring darwin,$(shell $(CC) -dumpmachine)),)
MACHINE := $(shell $(CC) -dumpmachine)
ifneq ($(findstring darwin,$(MACHINE)),)
SOEXT = dylib
SOEXTVER_MAJOR = $(SONAME_MAJOR).$(SOEXT)
SOEXTVER = $(SONAME_MAJOR).$(SONAME_MINOR).$(SOEXT)
LINKSHARED += -dynamiclib -Wl,-install_name,$(LIBDIR)/libtree-sitter.$(SOEXTVER)
else ifneq ($(findstring mingw32,$(MACHINE)),)
SOEXT = dll
LINKSHARED += -s -shared -Wl,--out-implib,libtree-sitter.dll.a
else
SOEXT = so
SOEXTVER_MAJOR = $(SOEXT).$(SONAME_MAJOR)
SOEXTVER = $(SOEXT).$(SONAME_MAJOR).$(SONAME_MINOR)
LINKSHARED += -shared -Wl,-soname,libtree-sitter.$(SOEXTVER)
endif
ifneq ($(filter $(shell uname),FreeBSD NetBSD DragonFly),)
PCLIBDIR := $(PREFIX)/libdata/pkgconfig
endif
endif
all: libtree-sitter.a libtree-sitter.$(SOEXT) tree-sitter.pc
@ -61,6 +63,10 @@ ifneq ($(STRIP),)
$(STRIP) $@
endif
ifneq ($(findstring mingw32,$(MACHINE)),)
libtree-sitter.dll.a: libtree-sitter.$(SOEXT)
endif
tree-sitter.pc: lib/tree-sitter.pc.in
sed -e 's|@PROJECT_VERSION@|$(VERSION)|' \
-e 's|@CMAKE_INSTALL_LIBDIR@|$(LIBDIR:$(PREFIX)/%=%)|' \
@ -69,17 +75,27 @@ tree-sitter.pc: lib/tree-sitter.pc.in
-e 's|@PROJECT_HOMEPAGE_URL@|$(HOMEPAGE_URL)|' \
-e 's|@CMAKE_INSTALL_PREFIX@|$(PREFIX)|' $< > $@
shared: libtree-sitter.$(SOEXT)
static: libtree-sitter.a
clean:
$(RM) $(OBJ) tree-sitter.pc libtree-sitter.a libtree-sitter.$(SOEXT)
$(RM) $(OBJ) tree-sitter.pc libtree-sitter.a libtree-sitter.$(SOEXT) libtree-stitter.dll.a
install: all
install -d '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)'
install -m644 lib/include/tree_sitter/api.h '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/api.h
install -m644 tree-sitter.pc '$(DESTDIR)$(PCLIBDIR)'/tree-sitter.pc
install -m644 libtree-sitter.a '$(DESTDIR)$(LIBDIR)'/libtree-sitter.a
ifneq ($(findstring mingw32,$(MACHINE)),)
install -d '$(DESTDIR)$(BINDIR)'
install -m755 libtree-sitter.dll '$(DESTDIR)$(BINDIR)'/libtree-sitter.dll
install -m755 libtree-sitter.dll.a '$(DESTDIR)$(LIBDIR)'/libtree-sitter.dll.a
else
install -m755 libtree-sitter.$(SOEXT) '$(DESTDIR)$(LIBDIR)'/libtree-sitter.$(SOEXTVER)
ln -sf libtree-sitter.$(SOEXTVER) '$(DESTDIR)$(LIBDIR)'/libtree-sitter.$(SOEXTVER_MAJOR)
ln -sf libtree-sitter.$(SOEXTVER_MAJOR) '$(DESTDIR)$(LIBDIR)'/libtree-sitter.$(SOEXT)
cd '$(DESTDIR)$(LIBDIR)' && ln -sf libtree-sitter.$(SOEXTVER) libtree-sitter.$(SOEXTVER_MAJOR)
cd '$(DESTDIR)$(LIBDIR)' && ln -sf libtree-sitter.$(SOEXTVER_MAJOR) libtree-sitter.$(SOEXT)
endif
uninstall:
$(RM) '$(DESTDIR)$(LIBDIR)'/libtree-sitter.a \
@ -88,8 +104,9 @@ uninstall:
'$(DESTDIR)$(LIBDIR)'/libtree-sitter.$(SOEXT) \
'$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/api.h \
'$(DESTDIR)$(PCLIBDIR)'/tree-sitter.pc
rmdir '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter
.PHONY: all install uninstall clean
.PHONY: all shared static install uninstall clean
##### Dev targets #####
@ -105,16 +122,20 @@ test-wasm:
lint:
cargo update --workspace --locked --quiet
cargo check --workspace --all-targets
cargo +nightly fmt --all --check
cargo +nightly clippy --workspace --all-targets -- -D warnings
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
lint-web:
npm --prefix lib/binding_web ci
npm --prefix lib/binding_web run lint
lint-toml:
taplo check
taplo format --check --diff
format:
cargo +nightly fmt --all
cargo fmt --all
taplo format
changelog:
@git-cliff --config .github/cliff.toml --prepend CHANGELOG.md --latest --github-token $(shell gh auth token)

View file

@ -1,33 +0,0 @@
// swift-tools-version: 5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "TreeSitter",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "TreeSitter",
targets: ["TreeSitter"]),
],
targets: [
.target(name: "TreeSitter",
path: "lib",
exclude: [
"src/unicode/ICU_SHA",
"src/unicode/README.md",
"src/unicode/LICENSE",
"src/wasm/stdlib-symbols.txt",
"src/lib.c",
],
sources: ["src"],
publicHeadersPath: "include",
cSettings: [
.headerSearchPath("src"),
.define("_POSIX_C_SOURCE", to: "200112L"),
.define("_DEFAULT_SOURCE"),
]),
],
cLanguageStandard: .c11
)

View file

@ -14,8 +14,8 @@ Tree-sitter is a parser generator tool and an incremental parsing library. It ca
## Links
- [Documentation](https://tree-sitter.github.io)
- [Rust binding](lib/binding_rust/README.md)
- [WASM binding](lib/binding_web/README.md)
- [Command-line interface](cli/README.md)
- [Wasm binding](lib/binding_web/README.md)
- [Command-line interface](crates/cli/README.md)
[discord]: https://img.shields.io/discord/1063097320771698699?logo=discord&label=discord
[matrix]: https://img.shields.io/matrix/tree-sitter-chat%3Amatrix.org?logo=matrix&label=matrix

231
build.zig
View file

@ -1,116 +1,147 @@
const std = @import("std");
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const wasm = b.option(bool, "enable-wasm", "Enable Wasm support") orelse false;
const shared = b.option(bool, "build-shared", "Build a shared library") orelse false;
const amalgamated = b.option(bool, "amalgamated", "Build using an amalgamated source") orelse false;
var threaded: std.Io.Threaded = .init(b.allocator, .{});
defer threaded.deinit();
const io = threaded.io();
const lib: *std.Build.Step.Compile = if (!shared) b.addStaticLibrary(.{
.name = "tree-sitter",
.target = target,
.optimize = optimize,
.link_libc = true,
}) else b.addSharedLibrary(.{
.name = "tree-sitter",
.pic = true,
.target = target,
.optimize = optimize,
.link_libc = true,
});
const wasm = b.option(bool, "enable-wasm", "Enable Wasm support") orelse false;
const shared = b.option(bool, "build-shared", "Build a shared library") orelse false;
const amalgamated = b.option(bool, "amalgamated", "Build using an amalgamated source") orelse false;
if (amalgamated) {
lib.addCSourceFile(.{
.file = b.path("lib/src/lib.c"),
.flags = &.{"-std=c11"},
var tree_sitter = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
.pic = if (shared) true else null,
});
} else {
lib.addCSourceFiles(.{
.root = b.path("lib/src"),
.files = try findSourceFiles(b),
.flags = &.{"-std=c11"},
const lib: *std.Build.Step.Compile = b.addLibrary(.{
.name = "tree-sitter",
.linkage = if (shared) .dynamic else .static,
.root_module = tree_sitter,
});
}
lib.addIncludePath(b.path("lib/include"));
lib.addIncludePath(b.path("lib/src"));
lib.addIncludePath(b.path("lib/src/wasm"));
lib.root_module.addCMacro("_POSIX_C_SOURCE", "200112L");
lib.root_module.addCMacro("_DEFAULT_SOURCE", "");
if (wasm) {
if (b.lazyDependency(wasmtimeDep(target.result), .{})) |wasmtime| {
lib.root_module.addCMacro("TREE_SITTER_FEATURE_WASM", "");
lib.addSystemIncludePath(wasmtime.path("include"));
lib.addLibraryPath(wasmtime.path("lib"));
lib.linkSystemLibrary("wasmtime");
if (amalgamated) {
tree_sitter.addCSourceFile(.{
.file = b.path("lib/src/lib.c"),
.flags = &.{"-std=c11"},
});
} else {
const files = try findSourceFiles(b, io);
defer b.allocator.free(files);
tree_sitter.addCSourceFiles(.{
.root = b.path("lib/src"),
.files = files,
.flags = &.{"-std=c11"},
});
}
}
lib.installHeadersDirectory(b.path("lib/include"), ".", .{});
tree_sitter.addIncludePath(b.path("lib/include"));
tree_sitter.addIncludePath(b.path("lib/src"));
tree_sitter.addIncludePath(b.path("lib/src/wasm"));
b.installArtifact(lib);
}
tree_sitter.addCMacro("_POSIX_C_SOURCE", "200112L");
tree_sitter.addCMacro("_DEFAULT_SOURCE", "");
tree_sitter.addCMacro("_BSD_SOURCE", "");
tree_sitter.addCMacro("_DARWIN_C_SOURCE", "");
fn wasmtimeDep(target: std.Target) []const u8 {
const arch = target.cpu.arch;
const os = target.os.tag;
const abi = target.abi;
return switch (os) {
.linux => switch (arch) {
.x86_64 => switch (abi) {
.gnu => "wasmtime_c_api_x86_64_linux",
.musl => "wasmtime_c_api_x86_64_musl",
.android => "wasmtime_c_api_x86_64_android",
else => null
},
.aarch64 => switch (abi) {
.gnu => "wasmtime_c_api_aarch64_linux",
.android => "wasmtime_c_api_aarch64_android",
else => null
},
.s390x => "wasmtime_c_api_s390x_linux",
.riscv64 => "wasmtime_c_api_riscv64gc_linux",
else => null
},
.windows => switch (arch) {
.x86_64 => switch (abi) {
.gnu => "wasmtime_c_api_x86_64_mingw",
.msvc => "wasmtime_c_api_x86_64_windows",
else => null
},
else => null
},
.macos => switch (arch) {
.x86_64 => "wasmtime_c_api_x86_64_macos",
.aarch64 => "wasmtime_c_api_aarch64_macos",
else => null
},
else => null
} orelse std.debug.panic(
"Unsupported target for wasmtime: {s}-{s}-{s}",
.{ @tagName(arch), @tagName(os), @tagName(abi) }
);
}
fn findSourceFiles(b: *std.Build) ![]const []const u8 {
var sources = std.ArrayList([]const u8).init(b.allocator);
var dir = try b.build_root.handle.openDir("lib/src", .{ .iterate = true });
var iter = dir.iterate();
defer dir.close();
while (try iter.next()) |entry| {
if (entry.kind != .file) continue;
const file = entry.name;
const ext = std.fs.path.extension(file);
if (std.mem.eql(u8, ext, ".c") and !std.mem.eql(u8, file, "lib.c")) {
try sources.append(b.dupe(file));
if (wasm) {
if (b.lazyDependency(wasmtimeDep(target.result), .{})) |wasmtime| {
tree_sitter.addCMacro("TREE_SITTER_FEATURE_WASM", "");
tree_sitter.addSystemIncludePath(wasmtime.path("include"));
tree_sitter.addLibraryPath(wasmtime.path("lib"));
if (shared) tree_sitter.linkSystemLibrary("wasmtime", .{});
}
}
}
return sources.items;
lib.installHeadersDirectory(b.path("lib/include"), ".", .{});
b.installArtifact(lib);
}
/// Get the name of the wasmtime dependency for this target.
pub fn wasmtimeDep(target: std.Target) []const u8 {
const arch = target.cpu.arch;
const os = target.os.tag;
const abi = target.abi;
return @as(?[]const u8, switch (os) {
.linux => switch (arch) {
.x86_64 => switch (abi) {
.gnu => "wasmtime_c_api_x86_64_linux",
.musl => "wasmtime_c_api_x86_64_musl",
.android => "wasmtime_c_api_x86_64_android",
else => null,
},
.aarch64 => switch (abi) {
.gnu => "wasmtime_c_api_aarch64_linux",
.musl => "wasmtime_c_api_aarch64_musl",
.android => "wasmtime_c_api_aarch64_android",
else => null,
},
.x86 => switch (abi) {
.gnu => "wasmtime_c_api_i686_linux",
else => null,
},
.arm => switch (abi) {
.gnueabi => "wasmtime_c_api_armv7_linux",
else => null,
},
.s390x => switch (abi) {
.gnu => "wasmtime_c_api_s390x_linux",
else => null,
},
.riscv64 => switch (abi) {
.gnu => "wasmtime_c_api_riscv64gc_linux",
else => null,
},
else => null,
},
.windows => switch (arch) {
.x86_64 => switch (abi) {
.gnu => "wasmtime_c_api_x86_64_mingw",
.msvc => "wasmtime_c_api_x86_64_windows",
else => null,
},
.aarch64 => switch (abi) {
.msvc => "wasmtime_c_api_aarch64_windows",
else => null,
},
.x86 => switch (abi) {
.msvc => "wasmtime_c_api_i686_windows",
else => null,
},
else => null,
},
.macos => switch (arch) {
.x86_64 => "wasmtime_c_api_x86_64_macos",
.aarch64 => "wasmtime_c_api_aarch64_macos",
else => null,
},
else => null,
}) orelse std.debug.panic(
"Unsupported target for wasmtime: {s}-{s}-{s}",
.{ @tagName(arch), @tagName(os), @tagName(abi) },
);
}
fn findSourceFiles(b: *std.Build, io: std.Io) ![]const []const u8 {
var sources: std.ArrayListUnmanaged([]const u8) = .empty;
var dir = try b.build_root.handle.openDir(io, "lib/src", .{ .iterate = true });
var iter = dir.iterate();
defer dir.close(io);
while (try iter.next(io)) |entry| {
if (entry.kind != .file) continue;
const file = entry.name;
const ext = std.fs.path.extension(file);
if (std.mem.eql(u8, ext, ".c") and !std.mem.eql(u8, file, "lib.c")) {
try sources.append(b.allocator, b.dupe(file));
}
}
return sources.toOwnedSlice(b.allocator);
}

View file

@ -1,69 +1,96 @@
.{
.name = "tree-sitter",
.version = "0.25.1",
.paths = .{
"build.zig",
"build.zig.zon",
"lib/src",
"lib/include",
"README.md",
"LICENSE",
},
.dependencies = .{
.wasmtime_c_api_aarch64_android = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v29.0.1/wasmtime-v29.0.1-aarch64-android-c-api.tar.xz",
.hash = "12204c77979ad8291c6e395d695a824fb053ffdfeb2cc21de95fffb09f77d77188d1",
.lazy = true,
.name = .tree_sitter,
.fingerprint = 0x841224b447ac0d4f,
.version = "0.28.0",
.minimum_zig_version = "0.16.0",
.paths = .{
"build.zig",
"build.zig.zon",
"lib/src",
"lib/include",
"README.md",
"LICENSE",
},
.wasmtime_c_api_aarch64_linux = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v29.0.1/wasmtime-v29.0.1-aarch64-linux-c-api.tar.xz",
.hash = "12203a8e3d823490186fb1e230d54f575148713088e914926305ee5678790b731bba",
.lazy = true,
.dependencies = .{
.wasmtime_c_api_aarch64_android = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-aarch64-android-c-api.tar.xz",
.hash = "N-V-__8AAIp_mQVzQOITXcYcWxYLJkvB1W1SvLlrdiU2G7fj",
.lazy = true,
},
.wasmtime_c_api_aarch64_linux = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-aarch64-linux-c-api.tar.xz",
.hash = "N-V-__8AAMztsgU5Aj4oI3MRHXJVe5rW72op-kT_78I3kZVM",
.lazy = true,
},
.wasmtime_c_api_aarch64_macos = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-aarch64-macos-c-api.tar.xz",
.hash = "N-V-__8AANZxOwT27sdrKxDDGGKsiwtcZlHy204xAWNIgDBH",
.lazy = true,
},
.wasmtime_c_api_aarch64_musl = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-aarch64-musl-c-api.tar.xz",
.hash = "N-V-__8AAJL1zQW9yxC98uc60lSuVUHhH77QHTto0zwIQnBj",
.lazy = true,
},
.wasmtime_c_api_aarch64_windows = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-aarch64-windows-c-api.zip",
.hash = "N-V-__8AAHRCtQU93hJcRFOgVcof3IQpRV9stT2Pp54wpJc2",
.lazy = true,
},
.wasmtime_c_api_armv7_linux = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-armv7-linux-c-api.tar.xz",
.hash = "N-V-__8AAJaW6gT8QdULOU0jxX4a_DOCA5YD6cxWBC8IqhQF",
.lazy = true,
},
.wasmtime_c_api_i686_linux = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-i686-linux-c-api.tar.xz",
.hash = "N-V-__8AANguMgVX4XMhdOVkdj4yfFKXrG8RTgZDs3nQB8J8",
.lazy = true,
},
.wasmtime_c_api_i686_windows = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-i686-windows-c-api.zip",
.hash = "N-V-__8AANY9ggXg4rK2_1o3EIlrCq124l5RfykPv-DPforq",
.lazy = true,
},
.wasmtime_c_api_riscv64gc_linux = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-riscv64gc-linux-c-api.tar.xz",
.hash = "N-V-__8AAPDtCAdQ0dD9Rs-qWl-kPr2c7L3PVsUVjwy12Iz1",
.lazy = true,
},
.wasmtime_c_api_s390x_linux = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-s390x-linux-c-api.tar.xz",
.hash = "N-V-__8AANA3BwY1ZOoGCWCR_tTY9G1vfIX128RGVxufN3ov",
.lazy = true,
},
.wasmtime_c_api_x86_64_android = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-x86_64-android-c-api.tar.xz",
.hash = "N-V-__8AAF4AIgY0ltjevj1ybGfvMU1ErPRnNve5X1TmvCru",
.lazy = true,
},
.wasmtime_c_api_x86_64_linux = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-x86_64-linux-c-api.tar.xz",
.hash = "N-V-__8AAIR0cAbjf3DkrTbu81Oq_zociz-0lCpb5DR0lIC9",
.lazy = true,
},
.wasmtime_c_api_x86_64_macos = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-x86_64-macos-c-api.tar.xz",
.hash = "N-V-__8AAFJ4lgRgCBnYdz8-Yfc4hLve45Hv-0RICAEuCp4s",
.lazy = true,
},
.wasmtime_c_api_x86_64_mingw = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-x86_64-mingw-c-api.zip",
.hash = "N-V-__8AAMxZxQZUpp1cU8J5zgLiMNq4e4dy0hcchlQFy03J",
.lazy = true,
},
.wasmtime_c_api_x86_64_musl = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-x86_64-musl-c-api.tar.xz",
.hash = "N-V-__8AAN5pWgZrZBt8VYWkN82WjyFe3DkGcE7uLn3jpt38",
.lazy = true,
},
.wasmtime_c_api_x86_64_windows = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v48.0.1/wasmtime-v48.0.1-x86_64-windows-c-api.zip",
.hash = "N-V-__8AAEIJkgaVHFETgakbognNUpFELuV17vpjw6NjsWhQ",
.lazy = true,
},
},
.wasmtime_c_api_aarch64_macos = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v29.0.1/wasmtime-v29.0.1-aarch64-macos-c-api.tar.xz",
.hash = "122043e8b19079b855b12674b9e3d4a28dc5c399c43b62fbeb8bdf0fdb4ef2d1d38c",
.lazy = true,
},
.wasmtime_c_api_riscv64gc_linux = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v29.0.1/wasmtime-v29.0.1-riscv64gc-linux-c-api.tar.xz",
.hash = "12209d07031cf33271bf4b0c63df407b535cd5d65c6402bd6f80d99de439d6feb89b",
.lazy = true,
},
.wasmtime_c_api_s390x_linux = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v29.0.1/wasmtime-v29.0.1-s390x-linux-c-api.tar.xz",
.hash = "122033f7d9b04f429063d9b2d9ac75a7a00fce02c425e578208f54ddc40edaa1e355",
.lazy = true,
},
.wasmtime_c_api_x86_64_android = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v29.0.1/wasmtime-v29.0.1-x86_64-android-c-api.tar.xz",
.hash = "122093cb33df8e09e70b2d1dc09897a0388915b942918389b10bf23f9684bdb6f047",
.lazy = true,
},
.wasmtime_c_api_x86_64_linux = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v29.0.1/wasmtime-v29.0.1-x86_64-linux-c-api.tar.xz",
.hash = "12209210346e94bf6ef8e249fa5d3f1a84f95050ed19665ac8422a15b5f2246d83af",
.lazy = true,
},
.wasmtime_c_api_x86_64_macos = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v29.0.1/wasmtime-v29.0.1-x86_64-macos-c-api.tar.xz",
.hash = "12208f875dd3a89092485762f3b184707b3cccae85a84e2ffd38c138cc3a3fd90447",
.lazy = true,
},
.wasmtime_c_api_x86_64_mingw = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v29.0.1/wasmtime-v29.0.1-x86_64-mingw-c-api.zip",
.hash = "1220bea757df3a777b6ec6322fc498e4ece20d466eedc5e2a3610b338849553cd94d",
.lazy = true,
},
.wasmtime_c_api_x86_64_musl = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v29.0.1/wasmtime-v29.0.1-x86_64-musl-c-api.tar.xz",
.hash = "1220c9596e6a63edcc3234c0611d0cbac724bf30ac9a0fbaf402c7da649b278b1322",
.lazy = true,
},
.wasmtime_c_api_x86_64_windows = .{
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v29.0.1/wasmtime-v29.0.1-x86_64-windows-c-api.zip",
.hash = "1220440ccb01d72989cf1a47728897a35fc8dd31673cce598f2d62c58e2c3228b0ed",
.lazy = true,
},
}
}

View file

@ -1,81 +0,0 @@
[package]
name = "tree-sitter-cli"
version.workspace = true
description = "CLI tool for developing, testing, and using Tree-sitter parsers"
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
readme = "README.md"
homepage.workspace = true
repository.workspace = true
license.workspace = true
keywords.workspace = true
categories.workspace = true
include = ["build.rs", "README.md", "benches/*", "src/**"]
[lints]
workspace = true
[[bin]]
name = "tree-sitter"
path = "src/main.rs"
doc = false
[[bench]]
name = "benchmark"
harness = false
[features]
wasm = ["tree-sitter/wasm", "tree-sitter-loader/wasm"]
[dependencies]
ansi_colours.workspace = true
anstyle.workspace = true
anyhow.workspace = true
bstr.workspace = true
clap.workspace = true
clap_complete.workspace = true
clap_complete_nushell.workspace = true
ctor.workspace = true
ctrlc.workspace = true
dialoguer.workspace = true
filetime.workspace = true
glob.workspace = true
heck.workspace = true
html-escape.workspace = true
indexmap.workspace = true
indoc.workspace = true
log.workspace = true
memchr.workspace = true
rand.workspace = true
regex.workspace = true
regex-syntax.workspace = true
rustc-hash.workspace = true
semver.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
similar.workspace = true
smallbitvec.workspace = true
streaming-iterator.workspace = true
tiny_http.workspace = true
url.workspace = true
walkdir.workspace = true
wasmparser.workspace = true
webbrowser.workspace = true
tree-sitter.workspace = true
tree-sitter-generate.workspace = true
tree-sitter-config.workspace = true
tree-sitter-highlight.workspace = true
tree-sitter-loader.workspace = true
tree-sitter-tags.workspace = true
[dev-dependencies]
encoding_rs = "0.8.35"
widestring = "1.1.0"
tree_sitter_proc_macro = { path = "src/tests/proc_macro", package = "tree-sitter-tests-proc-macro" }
tempfile.workspace = true
pretty_assertions.workspace = true
unindent.workspace = true

View file

@ -1,32 +0,0 @@
use std::{env, path::PathBuf, process::Command};
fn main() {
if let Some(git_sha) = read_git_sha() {
println!("cargo:rustc-env=BUILD_SHA={git_sha}");
}
}
// This is copied from the build.rs in parent directory. This should be updated if the
// parent build.rs gets fixes.
fn read_git_sha() -> Option<String> {
let crate_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
if !crate_path
.parent()?
.parent()
.is_some_and(|p| p.join(".git").exists())
{
return None;
}
Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(crate_path)
.output()
.map_or(None, |output| {
if !output.status.success() {
return None;
}
Some(String::from_utf8_lossy(&output.stdout).to_string())
})
}

View file

@ -1,79 +0,0 @@
use std::fmt;
use crate::{
grammars::LexicalGrammar,
rules::Symbol,
tables::{ParseStateId, ParseTable},
};
pub struct CoincidentTokenIndex<'a> {
entries: Vec<Vec<ParseStateId>>,
grammar: &'a LexicalGrammar,
n: usize,
}
impl<'a> CoincidentTokenIndex<'a> {
pub fn new(table: &ParseTable, lexical_grammar: &'a LexicalGrammar) -> Self {
let n = lexical_grammar.variables.len();
let mut result = Self {
n,
grammar: lexical_grammar,
entries: vec![Vec::new(); n * n],
};
for (i, state) in table.states.iter().enumerate() {
for symbol in state.terminal_entries.keys() {
if symbol.is_terminal() {
for other_symbol in state.terminal_entries.keys() {
if other_symbol.is_terminal() {
let index = result.index(symbol.index, other_symbol.index);
if result.entries[index].last().copied() != Some(i) {
result.entries[index].push(i);
}
}
}
}
}
}
result
}
pub fn states_with(&self, a: Symbol, b: Symbol) -> &[ParseStateId] {
&self.entries[self.index(a.index, b.index)]
}
pub fn contains(&self, a: Symbol, b: Symbol) -> bool {
!self.entries[self.index(a.index, b.index)].is_empty()
}
#[must_use]
const fn index(&self, a: usize, b: usize) -> usize {
if a < b {
a * self.n + b
} else {
b * self.n + a
}
}
}
impl fmt::Debug for CoincidentTokenIndex<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "CoincidentTokenIndex {{")?;
writeln!(f, " entries: {{")?;
for i in 0..self.n {
writeln!(f, " {}: {{", self.grammar.variables[i].name)?;
for j in 0..self.n {
writeln!(
f,
" {}: {:?},",
self.grammar.variables[j].name,
self.entries[self.index(i, j)].len()
)?;
}
writeln!(f, " }},")?;
}
write!(f, " }},")?;
write!(f, "}}")?;
Ok(())
}
}

View file

@ -1,462 +0,0 @@
use std::{
cmp::Ordering,
fmt,
hash::{Hash, Hasher},
sync::LazyLock,
};
use crate::{
grammars::{
LexicalGrammar, Production, ProductionStep, ReservedWordSetId, SyntaxGrammar,
NO_RESERVED_WORDS,
},
rules::{Associativity, Precedence, Symbol, SymbolType, TokenSet},
};
static START_PRODUCTION: LazyLock<Production> = LazyLock::new(|| Production {
dynamic_precedence: 0,
steps: vec![ProductionStep {
symbol: Symbol {
index: 0,
kind: SymbolType::NonTerminal,
},
precedence: Precedence::None,
associativity: None,
alias: None,
field_name: None,
reserved_word_set_id: NO_RESERVED_WORDS,
}],
});
/// A [`ParseItem`] represents an in-progress match of a single production in a grammar.
#[derive(Clone, Copy, Debug)]
pub struct ParseItem<'a> {
/// The index of the parent rule within the grammar.
pub variable_index: u32,
/// The number of symbols that have already been matched.
pub step_index: u32,
/// The production being matched.
pub production: &'a Production,
/// A boolean indicating whether any of the already-matched children were
/// hidden nodes and had fields. Ordinarily, a parse item's behavior is not
/// affected by the symbols of its preceding children; it only needs to
/// keep track of their fields and aliases.
///
/// Take for example these two items:
/// X -> a b • c
/// X -> a g • c
///
/// They can be considered equivalent, for the purposes of parse table
/// generation, because they entail the same actions. But if this flag is
/// true, then the item's set of inherited fields may depend on the specific
/// symbols of its preceding children.
pub has_preceding_inherited_fields: bool,
}
/// A [`ParseItemSet`] represents a set of in-progress matches of productions in a
/// grammar, and for each in-progress match, a set of "lookaheads" - tokens that
/// are allowed to *follow* the in-progress rule. This object corresponds directly
/// to a state in the final parse table.
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct ParseItemSet<'a> {
pub entries: Vec<ParseItemSetEntry<'a>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParseItemSetEntry<'a> {
pub item: ParseItem<'a>,
pub lookaheads: TokenSet,
pub following_reserved_word_set: ReservedWordSetId,
}
/// A [`ParseItemSetCore`] is like a [`ParseItemSet`], but without the lookahead
/// information. Parse states with the same core are candidates for merging.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParseItemSetCore<'a> {
pub entries: Vec<ParseItem<'a>>,
}
pub struct ParseItemDisplay<'a>(
pub &'a ParseItem<'a>,
pub &'a SyntaxGrammar,
pub &'a LexicalGrammar,
);
pub struct TokenSetDisplay<'a>(
pub &'a TokenSet,
pub &'a SyntaxGrammar,
pub &'a LexicalGrammar,
);
pub struct ParseItemSetDisplay<'a>(
pub &'a ParseItemSet<'a>,
pub &'a SyntaxGrammar,
pub &'a LexicalGrammar,
);
impl<'a> ParseItem<'a> {
pub fn start() -> Self {
ParseItem {
variable_index: u32::MAX,
production: &START_PRODUCTION,
step_index: 0,
has_preceding_inherited_fields: false,
}
}
pub fn step(&self) -> Option<&'a ProductionStep> {
self.production.steps.get(self.step_index as usize)
}
pub fn symbol(&self) -> Option<Symbol> {
self.step().map(|step| step.symbol)
}
pub fn associativity(&self) -> Option<Associativity> {
self.prev_step().and_then(|step| step.associativity)
}
pub fn precedence(&self) -> &Precedence {
self.prev_step()
.map_or(&Precedence::None, |step| &step.precedence)
}
pub fn prev_step(&self) -> Option<&'a ProductionStep> {
if self.step_index > 0 {
Some(&self.production.steps[self.step_index as usize - 1])
} else {
None
}
}
#[must_use]
pub fn is_done(&self) -> bool {
self.step_index as usize == self.production.steps.len()
}
#[must_use]
pub const fn is_augmented(&self) -> bool {
self.variable_index == u32::MAX
}
/// Create an item like this one, but advanced by one step.
#[must_use]
pub const fn successor(&self) -> Self {
ParseItem {
variable_index: self.variable_index,
production: self.production,
step_index: self.step_index + 1,
has_preceding_inherited_fields: self.has_preceding_inherited_fields,
}
}
/// Create an item identical to this one, but with a different production.
/// This is used when dynamically "inlining" certain symbols in a production.
pub const fn substitute_production(&self, production: &'a Production) -> Self {
let mut result = *self;
result.production = production;
result
}
}
impl<'a> ParseItemSet<'a> {
pub fn insert(&mut self, item: ParseItem<'a>) -> &mut ParseItemSetEntry<'a> {
match self.entries.binary_search_by(|e| e.item.cmp(&item)) {
Err(i) => {
self.entries.insert(
i,
ParseItemSetEntry {
item,
lookaheads: TokenSet::new(),
following_reserved_word_set: ReservedWordSetId::default(),
},
);
&mut self.entries[i]
}
Ok(i) => &mut self.entries[i],
}
}
pub fn core(&self) -> ParseItemSetCore<'a> {
ParseItemSetCore {
entries: self.entries.iter().map(|e| e.item).collect(),
}
}
}
impl fmt::Display for ParseItemDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
if self.0.is_augmented() {
write!(f, "START →")?;
} else {
write!(
f,
"{} →",
self.1.variables[self.0.variable_index as usize].name
)?;
}
for (i, step) in self.0.production.steps.iter().enumerate() {
if i == self.0.step_index as usize {
write!(f, "")?;
if !step.precedence.is_none()
|| step.associativity.is_some()
|| step.reserved_word_set_id != ReservedWordSetId::default()
{
write!(f, " (")?;
if step.precedence.is_none() {
write!(f, " {}", step.precedence)?;
}
if let Some(associativity) = step.associativity {
write!(f, " {associativity:?}")?;
}
if step.reserved_word_set_id != ReservedWordSetId::default() {
write!(f, "reserved: {}", step.reserved_word_set_id)?;
}
write!(f, " )")?;
}
}
write!(f, " ")?;
if step.symbol.is_terminal() {
if let Some(variable) = self.2.variables.get(step.symbol.index) {
write!(f, "{}", variable.name)?;
} else {
write!(f, "terminal-{}", step.symbol.index)?;
}
} else if step.symbol.is_external() {
write!(f, "{}", self.1.external_tokens[step.symbol.index].name)?;
} else {
write!(f, "{}", self.1.variables[step.symbol.index].name)?;
}
if let Some(alias) = &step.alias {
write!(f, "@{}", alias.value)?;
}
}
if self.0.is_done() {
write!(f, "")?;
if let Some(step) = self.0.production.steps.last() {
if let Some(associativity) = step.associativity {
if step.precedence.is_none() {
write!(f, " ({associativity:?})")?;
} else {
write!(f, " ({} {associativity:?})", step.precedence)?;
}
} else if !step.precedence.is_none() {
write!(f, " ({})", step.precedence)?;
}
}
}
Ok(())
}
}
const fn escape_invisible(c: char) -> Option<&'static str> {
Some(match c {
'\n' => "\\n",
'\r' => "\\r",
'\t' => "\\t",
'\0' => "\\0",
'\\' => "\\\\",
'\x0b' => "\\v",
'\x0c' => "\\f",
_ => return None,
})
}
fn display_variable_name(source: &str) -> String {
source
.chars()
.fold(String::with_capacity(source.len()), |mut acc, c| {
if let Some(esc) = escape_invisible(c) {
acc.push_str(esc);
} else {
acc.push(c);
}
acc
})
}
impl fmt::Display for TokenSetDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "[")?;
for (i, symbol) in self.0.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
if symbol.is_terminal() {
if let Some(variable) = self.2.variables.get(symbol.index) {
write!(f, "{}", display_variable_name(&variable.name))?;
} else {
write!(f, "terminal-{}", symbol.index)?;
}
} else if symbol.is_external() {
write!(f, "{}", self.1.external_tokens[symbol.index].name)?;
} else {
write!(f, "{}", self.1.variables[symbol.index].name)?;
}
}
write!(f, "]")?;
Ok(())
}
}
impl fmt::Display for ParseItemSetDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
for entry in &self.0.entries {
write!(
f,
"{}\t{}",
ParseItemDisplay(&entry.item, self.1, self.2),
TokenSetDisplay(&entry.lookaheads, self.1, self.2),
)?;
if entry.following_reserved_word_set != ReservedWordSetId::default() {
write!(
f,
"\treserved word set: {}",
entry.following_reserved_word_set
)?;
}
writeln!(f)?;
}
Ok(())
}
}
impl Hash for ParseItem<'_> {
fn hash<H: Hasher>(&self, hasher: &mut H) {
hasher.write_u32(self.variable_index);
hasher.write_u32(self.step_index);
hasher.write_i32(self.production.dynamic_precedence);
hasher.write_usize(self.production.steps.len());
hasher.write_i32(i32::from(self.has_preceding_inherited_fields));
self.precedence().hash(hasher);
self.associativity().hash(hasher);
// The already-matched children don't play any role in the parse state for
// this item, unless any of the following are true:
// * the children have fields
// * the children have aliases
// * the children are hidden and represent rules that have fields.
// See the docs for `has_preceding_inherited_fields`.
for step in &self.production.steps[0..self.step_index as usize] {
step.alias.hash(hasher);
step.field_name.hash(hasher);
if self.has_preceding_inherited_fields {
step.symbol.hash(hasher);
}
}
for step in &self.production.steps[self.step_index as usize..] {
step.hash(hasher);
}
}
}
impl PartialEq for ParseItem<'_> {
fn eq(&self, other: &Self) -> bool {
if self.variable_index != other.variable_index
|| self.step_index != other.step_index
|| self.production.dynamic_precedence != other.production.dynamic_precedence
|| self.production.steps.len() != other.production.steps.len()
|| self.precedence() != other.precedence()
|| self.associativity() != other.associativity()
|| self.has_preceding_inherited_fields != other.has_preceding_inherited_fields
{
return false;
}
for (i, step) in self.production.steps.iter().enumerate() {
// See the previous comment (in the `Hash::hash` impl) regarding comparisons
// of parse items' already-completed steps.
if i < self.step_index as usize {
if step.alias != other.production.steps[i].alias {
return false;
}
if step.field_name != other.production.steps[i].field_name {
return false;
}
if self.has_preceding_inherited_fields
&& step.symbol != other.production.steps[i].symbol
{
return false;
}
} else if *step != other.production.steps[i] {
return false;
}
}
true
}
}
impl Ord for ParseItem<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.step_index
.cmp(&other.step_index)
.then_with(|| self.variable_index.cmp(&other.variable_index))
.then_with(|| {
self.production
.dynamic_precedence
.cmp(&other.production.dynamic_precedence)
})
.then_with(|| {
self.production
.steps
.len()
.cmp(&other.production.steps.len())
})
.then_with(|| self.precedence().cmp(other.precedence()))
.then_with(|| self.associativity().cmp(&other.associativity()))
.then_with(|| {
for (i, step) in self.production.steps.iter().enumerate() {
// See the previous comment (in the `Hash::hash` impl) regarding comparisons
// of parse items' already-completed steps.
let o = if i < self.step_index as usize {
step.alias
.cmp(&other.production.steps[i].alias)
.then_with(|| {
step.field_name.cmp(&other.production.steps[i].field_name)
})
} else {
step.cmp(&other.production.steps[i])
};
if o != Ordering::Equal {
return o;
}
}
Ordering::Equal
})
}
}
impl PartialOrd for ParseItem<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Eq for ParseItem<'_> {}
impl Hash for ParseItemSet<'_> {
fn hash<H: Hasher>(&self, hasher: &mut H) {
hasher.write_usize(self.entries.len());
for entry in &self.entries {
entry.item.hash(hasher);
entry.lookaheads.hash(hasher);
entry.following_reserved_word_set.hash(hasher);
}
}
}
impl Hash for ParseItemSetCore<'_> {
fn hash<H: Hasher>(&self, hasher: &mut H) {
hasher.write_usize(self.entries.len());
for item in &self.entries {
item.hash(hasher);
}
}
}

View file

@ -1,397 +0,0 @@
use std::{
collections::{HashMap, HashSet},
fmt,
};
use super::item::{ParseItem, ParseItemDisplay, ParseItemSet, ParseItemSetEntry, TokenSetDisplay};
use crate::{
grammars::{InlinedProductionMap, LexicalGrammar, ReservedWordSetId, SyntaxGrammar},
rules::{Symbol, SymbolType, TokenSet},
};
#[derive(Clone, Debug, PartialEq, Eq)]
struct TransitiveClosureAddition<'a> {
item: ParseItem<'a>,
info: FollowSetInfo,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
struct FollowSetInfo {
lookaheads: TokenSet,
reserved_lookaheads: ReservedWordSetId,
propagates_lookaheads: bool,
}
pub struct ParseItemSetBuilder<'a> {
syntax_grammar: &'a SyntaxGrammar,
lexical_grammar: &'a LexicalGrammar,
first_sets: HashMap<Symbol, TokenSet>,
reserved_first_sets: HashMap<Symbol, ReservedWordSetId>,
last_sets: HashMap<Symbol, TokenSet>,
inlines: &'a InlinedProductionMap,
transitive_closure_additions: Vec<Vec<TransitiveClosureAddition<'a>>>,
}
fn find_or_push<T: Eq>(vector: &mut Vec<T>, value: T) {
if !vector.contains(&value) {
vector.push(value);
}
}
impl<'a> ParseItemSetBuilder<'a> {
pub fn new(
syntax_grammar: &'a SyntaxGrammar,
lexical_grammar: &'a LexicalGrammar,
inlines: &'a InlinedProductionMap,
) -> Self {
let mut result = Self {
syntax_grammar,
lexical_grammar,
first_sets: HashMap::new(),
reserved_first_sets: HashMap::new(),
last_sets: HashMap::new(),
inlines,
transitive_closure_additions: vec![Vec::new(); syntax_grammar.variables.len()],
};
// For each grammar symbol, populate the FIRST and LAST sets: the set of
// terminals that appear at the beginning and end that symbol's productions,
// respectively.
// For a terminal symbol, the FIRST and LAST sets just consist of the
// terminal itself.
for i in 0..lexical_grammar.variables.len() {
let symbol = Symbol::terminal(i);
let mut set = TokenSet::new();
set.insert(symbol);
result.first_sets.insert(symbol, set.clone());
result.last_sets.insert(symbol, set);
result
.reserved_first_sets
.insert(symbol, ReservedWordSetId::default());
}
for i in 0..syntax_grammar.external_tokens.len() {
let symbol = Symbol::external(i);
let mut set = TokenSet::new();
set.insert(symbol);
result.first_sets.insert(symbol, set.clone());
result.last_sets.insert(symbol, set);
result
.reserved_first_sets
.insert(symbol, ReservedWordSetId::default());
}
// The FIRST set of a non-terminal `i` is the union of the the FIRST sets
// of all the symbols that appear at the beginnings of i's productions. Some
// of these symbols may themselves be non-terminals, so this is a recursive
// definition.
//
// Rather than computing these sets using recursion, we use an explicit stack
// called `symbols_to_process`.
let mut symbols_to_process = Vec::new();
let mut processed_non_terminals = HashSet::new();
for i in 0..syntax_grammar.variables.len() {
let symbol = Symbol::non_terminal(i);
let first_set = result.first_sets.entry(symbol).or_default();
let reserved_first_set = result.reserved_first_sets.entry(symbol).or_default();
processed_non_terminals.clear();
symbols_to_process.clear();
symbols_to_process.push(symbol);
while let Some(sym) = symbols_to_process.pop() {
for production in &syntax_grammar.variables[sym.index].productions {
if let Some(step) = production.steps.first() {
if step.symbol.is_terminal() || step.symbol.is_external() {
first_set.insert(step.symbol);
} else if processed_non_terminals.insert(step.symbol) {
symbols_to_process.push(step.symbol);
}
*reserved_first_set = (*reserved_first_set).max(step.reserved_word_set_id);
}
}
}
// The LAST set is defined in a similar way to the FIRST set.
let last_set = result.last_sets.entry(symbol).or_default();
processed_non_terminals.clear();
symbols_to_process.clear();
symbols_to_process.push(symbol);
while let Some(sym) = symbols_to_process.pop() {
for production in &syntax_grammar.variables[sym.index].productions {
if let Some(step) = production.steps.last() {
if step.symbol.is_terminal() || step.symbol.is_external() {
last_set.insert(step.symbol);
} else if processed_non_terminals.insert(step.symbol) {
symbols_to_process.push(step.symbol);
}
}
}
}
}
// To compute an item set's transitive closure, we find each item in the set
// whose next symbol is a non-terminal, and we add new items to the set for
// each of that symbol's productions. These productions might themselves begin
// with non-terminals, so the process continues recursively. In this process,
// the total set of entries that get added depends only on two things:
//
// * the non-terminal symbol that occurs next in each item
//
// * the set of terminals that can follow that non-terminal symbol in the item
//
// So we can avoid a lot of duplicated recursive work by precomputing, for each
// non-terminal symbol `i`, a final list of *additions* that must be made to an
// item set when symbol `i` occurs as the next symbol in one if its core items.
// The structure of a precomputed *addition* is as follows:
//
// * `item` - the new item that must be added as part of the expansion of the symbol `i`.
//
// * `lookaheads` - the set of possible lookahead tokens that can always come after `item`
// in an expansion of symbol `i`.
//
// * `reserved_lookaheads` - the set of reserved lookahead lookahead tokens that can
// always come after `item` in the expansion of symbol `i`.
//
// * `propagates_lookaheads` - a boolean indicating whether or not `item` can occur at the
// *end* of the expansion of symbol `i`, so that i's own current lookahead tokens can
// occur after `item`.
//
// Rather than computing these additions recursively, we use an explicit stack.
let empty_lookaheads = TokenSet::new();
let mut stack = Vec::new();
let mut follow_set_info_by_non_terminal = HashMap::<usize, FollowSetInfo>::new();
for i in 0..syntax_grammar.variables.len() {
// First, build up a map whose keys are all of the non-terminals that can
// appear at the beginning of non-terminal `i`, and whose values store
// information about the tokens that can follow those non-terminals.
stack.clear();
stack.push((i, &empty_lookaheads, ReservedWordSetId::default(), true));
follow_set_info_by_non_terminal.clear();
while let Some((sym_ix, lookaheads, reserved_word_set_id, propagates_lookaheads)) =
stack.pop()
{
let mut did_add = false;
let info = follow_set_info_by_non_terminal.entry(sym_ix).or_default();
did_add |= info.lookaheads.insert_all(lookaheads);
if reserved_word_set_id > info.reserved_lookaheads {
info.reserved_lookaheads = reserved_word_set_id;
did_add = true;
}
did_add |= propagates_lookaheads && !info.propagates_lookaheads;
info.propagates_lookaheads |= propagates_lookaheads;
if !did_add {
continue;
}
for production in &syntax_grammar.variables[sym_ix].productions {
if let Some(symbol) = production.first_symbol() {
if symbol.is_non_terminal() {
if let Some(next_step) = production.steps.get(1) {
stack.push((
symbol.index,
&result.first_sets[&next_step.symbol],
result.reserved_first_sets[&next_step.symbol],
false,
));
} else {
stack.push((
symbol.index,
lookaheads,
reserved_word_set_id,
propagates_lookaheads,
));
}
}
}
}
}
// Store all of those non-terminals' productions, along with their associated
// lookahead info, as *additions* associated with non-terminal `i`.
let additions_for_non_terminal = &mut result.transitive_closure_additions[i];
for (&variable_index, follow_set_info) in &follow_set_info_by_non_terminal {
let variable = &syntax_grammar.variables[variable_index];
let non_terminal = Symbol::non_terminal(variable_index);
let variable_index = variable_index as u32;
if syntax_grammar.variables_to_inline.contains(&non_terminal) {
continue;
}
for production in &variable.productions {
let item = ParseItem {
variable_index,
production,
step_index: 0,
has_preceding_inherited_fields: false,
};
if let Some(inlined_productions) =
inlines.inlined_productions(item.production, item.step_index)
{
for production in inlined_productions {
find_or_push(
additions_for_non_terminal,
TransitiveClosureAddition {
item: item.substitute_production(production),
info: follow_set_info.clone(),
},
);
}
} else {
find_or_push(
additions_for_non_terminal,
TransitiveClosureAddition {
item,
info: follow_set_info.clone(),
},
);
}
}
}
}
result
}
pub fn transitive_closure(&self, item_set: &ParseItemSet<'a>) -> ParseItemSet<'a> {
let mut result = ParseItemSet::default();
for entry in &item_set.entries {
if let Some(productions) = self
.inlines
.inlined_productions(entry.item.production, entry.item.step_index)
{
for production in productions {
self.add_item(
&mut result,
&ParseItemSetEntry {
item: entry.item.substitute_production(production),
lookaheads: entry.lookaheads.clone(),
following_reserved_word_set: entry.following_reserved_word_set,
},
);
}
} else {
self.add_item(&mut result, entry);
}
}
result
}
pub fn first_set(&self, symbol: &Symbol) -> &TokenSet {
&self.first_sets[symbol]
}
pub fn reserved_first_set(&self, symbol: &Symbol) -> Option<&TokenSet> {
let id = *self.reserved_first_sets.get(symbol)?;
Some(&self.syntax_grammar.reserved_word_sets[id.0])
}
pub fn last_set(&self, symbol: &Symbol) -> &TokenSet {
&self.last_sets[symbol]
}
fn add_item(&self, set: &mut ParseItemSet<'a>, entry: &ParseItemSetEntry<'a>) {
if let Some(step) = entry.item.step() {
if step.symbol.is_non_terminal() {
let next_step = entry.item.successor().step();
// Determine which tokens can follow this non-terminal.
let (following_tokens, following_reserved_tokens) =
if let Some(next_step) = next_step {
(
self.first_sets.get(&next_step.symbol).unwrap(),
*self.reserved_first_sets.get(&next_step.symbol).unwrap(),
)
} else {
(&entry.lookaheads, entry.following_reserved_word_set)
};
// Use the pre-computed *additions* to expand the non-terminal.
for addition in &self.transitive_closure_additions[step.symbol.index] {
let entry = set.insert(addition.item);
entry.lookaheads.insert_all(&addition.info.lookaheads);
if let Some(word_token) = self.syntax_grammar.word_token {
if addition.info.lookaheads.contains(&word_token) {
entry.following_reserved_word_set = entry
.following_reserved_word_set
.max(addition.info.reserved_lookaheads);
}
}
if addition.info.propagates_lookaheads {
entry.lookaheads.insert_all(following_tokens);
if let Some(word_token) = self.syntax_grammar.word_token {
if following_tokens.contains(&word_token) {
entry.following_reserved_word_set = entry
.following_reserved_word_set
.max(following_reserved_tokens);
}
}
}
}
}
}
let e = set.insert(entry.item);
e.lookaheads.insert_all(&entry.lookaheads);
e.following_reserved_word_set = e
.following_reserved_word_set
.max(entry.following_reserved_word_set);
}
}
impl fmt::Debug for ParseItemSetBuilder<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "ParseItemSetBuilder {{")?;
writeln!(f, " first_sets: {{")?;
for (symbol, first_set) in &self.first_sets {
let name = match symbol.kind {
SymbolType::NonTerminal => &self.syntax_grammar.variables[symbol.index].name,
SymbolType::External => &self.syntax_grammar.external_tokens[symbol.index].name,
SymbolType::Terminal => &self.lexical_grammar.variables[symbol.index].name,
SymbolType::End | SymbolType::EndOfNonTerminalExtra => "END",
};
writeln!(
f,
" first({name:?}): {}",
TokenSetDisplay(first_set, self.syntax_grammar, self.lexical_grammar)
)?;
}
writeln!(f, " }}")?;
writeln!(f, " last_sets: {{")?;
for (symbol, last_set) in &self.last_sets {
let name = match symbol.kind {
SymbolType::NonTerminal => &self.syntax_grammar.variables[symbol.index].name,
SymbolType::External => &self.syntax_grammar.external_tokens[symbol.index].name,
SymbolType::Terminal => &self.lexical_grammar.variables[symbol.index].name,
SymbolType::End | SymbolType::EndOfNonTerminalExtra => "END",
};
writeln!(
f,
" last({name:?}): {}",
TokenSetDisplay(last_set, self.syntax_grammar, self.lexical_grammar)
)?;
}
writeln!(f, " }}")?;
writeln!(f, " additions: {{")?;
for (i, variable) in self.syntax_grammar.variables.iter().enumerate() {
writeln!(f, " {}: {{", variable.name)?;
for addition in &self.transitive_closure_additions[i] {
writeln!(
f,
" {}",
ParseItemDisplay(&addition.item, self.syntax_grammar, self.lexical_grammar)
)?;
}
writeln!(f, " }},")?;
}
write!(f, " }},")?;
write!(f, "}}")?;
Ok(())
}
}

View file

@ -1,492 +0,0 @@
use std::{
collections::{HashMap, HashSet},
mem,
};
use log::info;
use super::token_conflicts::TokenConflictMap;
use crate::{
dedup::split_state_id_groups,
grammars::{LexicalGrammar, SyntaxGrammar, VariableType},
rules::{AliasMap, Symbol, TokenSet},
tables::{GotoAction, ParseAction, ParseState, ParseStateId, ParseTable, ParseTableEntry},
};
pub fn minimize_parse_table(
parse_table: &mut ParseTable,
syntax_grammar: &SyntaxGrammar,
lexical_grammar: &LexicalGrammar,
simple_aliases: &AliasMap,
token_conflict_map: &TokenConflictMap,
keywords: &TokenSet,
) {
let mut minimizer = Minimizer {
parse_table,
syntax_grammar,
lexical_grammar,
token_conflict_map,
keywords,
simple_aliases,
};
minimizer.merge_compatible_states();
minimizer.remove_unit_reductions();
minimizer.remove_unused_states();
minimizer.reorder_states_by_descending_size();
}
struct Minimizer<'a> {
parse_table: &'a mut ParseTable,
syntax_grammar: &'a SyntaxGrammar,
lexical_grammar: &'a LexicalGrammar,
token_conflict_map: &'a TokenConflictMap<'a>,
keywords: &'a TokenSet,
simple_aliases: &'a AliasMap,
}
impl Minimizer<'_> {
fn remove_unit_reductions(&mut self) {
let mut aliased_symbols = HashSet::new();
for variable in &self.syntax_grammar.variables {
for production in &variable.productions {
for step in &production.steps {
if step.alias.is_some() {
aliased_symbols.insert(step.symbol);
}
}
}
}
let mut unit_reduction_symbols_by_state = HashMap::new();
for (i, state) in self.parse_table.states.iter().enumerate() {
let mut only_unit_reductions = true;
let mut unit_reduction_symbol = None;
for (_, entry) in &state.terminal_entries {
for action in &entry.actions {
match action {
ParseAction::ShiftExtra => continue,
ParseAction::Reduce {
child_count: 1,
production_id: 0,
symbol,
..
} if !self.simple_aliases.contains_key(symbol)
&& !self.syntax_grammar.supertype_symbols.contains(symbol)
&& !self.syntax_grammar.extra_symbols.contains(symbol)
&& !aliased_symbols.contains(symbol)
&& self.syntax_grammar.variables[symbol.index].kind
!= VariableType::Named
&& (unit_reduction_symbol.is_none()
|| unit_reduction_symbol == Some(symbol)) =>
{
unit_reduction_symbol = Some(symbol);
continue;
}
_ => {}
}
only_unit_reductions = false;
break;
}
if !only_unit_reductions {
break;
}
}
if let Some(symbol) = unit_reduction_symbol {
if only_unit_reductions {
unit_reduction_symbols_by_state.insert(i, *symbol);
}
}
}
for state in &mut self.parse_table.states {
let mut done = false;
while !done {
done = true;
state.update_referenced_states(|other_state_id, state| {
unit_reduction_symbols_by_state.get(&other_state_id).map_or(
other_state_id,
|symbol| {
done = false;
match state.nonterminal_entries.get(symbol) {
Some(GotoAction::Goto(state_id)) => *state_id,
_ => other_state_id,
}
},
)
});
}
}
}
fn merge_compatible_states(&mut self) {
let core_count = 1 + self
.parse_table
.states
.iter()
.map(|state| state.core_id)
.max()
.unwrap();
// Initially group the states by their parse item set core.
let mut group_ids_by_state_id = Vec::with_capacity(self.parse_table.states.len());
let mut state_ids_by_group_id = vec![Vec::<ParseStateId>::new(); core_count];
for (i, state) in self.parse_table.states.iter().enumerate() {
state_ids_by_group_id[state.core_id].push(i);
group_ids_by_state_id.push(state.core_id);
}
split_state_id_groups(
&self.parse_table.states,
&mut state_ids_by_group_id,
&mut group_ids_by_state_id,
0,
|left, right, groups| self.states_conflict(left, right, groups),
);
while split_state_id_groups(
&self.parse_table.states,
&mut state_ids_by_group_id,
&mut group_ids_by_state_id,
0,
|left, right, groups| self.state_successors_differ(left, right, groups),
) {}
let error_group_index = state_ids_by_group_id
.iter()
.position(|g| g.contains(&0))
.unwrap();
let start_group_index = state_ids_by_group_id
.iter()
.position(|g| g.contains(&1))
.unwrap();
state_ids_by_group_id.swap(error_group_index, 0);
state_ids_by_group_id.swap(start_group_index, 1);
// Create a list of new parse states: one state for each group of old states.
let mut new_states = Vec::with_capacity(state_ids_by_group_id.len());
for state_ids in &state_ids_by_group_id {
// Initialize the new state based on the first old state in the group.
let mut parse_state = mem::take(&mut self.parse_table.states[state_ids[0]]);
// Extend the new state with all of the actions from the other old states
// in the group.
for state_id in &state_ids[1..] {
let other_parse_state = mem::take(&mut self.parse_table.states[*state_id]);
parse_state
.terminal_entries
.extend(other_parse_state.terminal_entries);
parse_state
.nonterminal_entries
.extend(other_parse_state.nonterminal_entries);
parse_state
.reserved_words
.insert_all(&other_parse_state.reserved_words);
for symbol in parse_state.terminal_entries.keys() {
parse_state.reserved_words.remove(symbol);
}
}
// Update the new state's outgoing references using the new grouping.
parse_state.update_referenced_states(|state_id, _| group_ids_by_state_id[state_id]);
new_states.push(parse_state);
}
self.parse_table.states = new_states;
}
fn states_conflict(
&self,
left_state: &ParseState,
right_state: &ParseState,
group_ids_by_state_id: &[ParseStateId],
) -> bool {
for (token, left_entry) in &left_state.terminal_entries {
if let Some(right_entry) = right_state.terminal_entries.get(token) {
if self.entries_conflict(
left_state.id,
right_state.id,
token,
left_entry,
right_entry,
group_ids_by_state_id,
) {
return true;
}
} else if self.token_conflicts(left_state.id, right_state.id, right_state, *token) {
return true;
}
}
for token in right_state.terminal_entries.keys() {
if !left_state.terminal_entries.contains_key(token)
&& self.token_conflicts(left_state.id, right_state.id, left_state, *token)
{
return true;
}
}
false
}
fn state_successors_differ(
&self,
state1: &ParseState,
state2: &ParseState,
group_ids_by_state_id: &[ParseStateId],
) -> bool {
for (token, entry1) in &state1.terminal_entries {
if let ParseAction::Shift { state: s1, .. } = entry1.actions.last().unwrap() {
if let Some(entry2) = state2.terminal_entries.get(token) {
if let ParseAction::Shift { state: s2, .. } = entry2.actions.last().unwrap() {
let group1 = group_ids_by_state_id[*s1];
let group2 = group_ids_by_state_id[*s2];
if group1 != group2 {
info!(
"split states {} {} - successors for {} are split: {s1} {s2}",
state1.id,
state2.id,
self.symbol_name(token),
);
return true;
}
}
}
}
}
for (symbol, s1) in &state1.nonterminal_entries {
if let Some(s2) = state2.nonterminal_entries.get(symbol) {
match (s1, s2) {
(GotoAction::ShiftExtra, GotoAction::ShiftExtra) => {}
(GotoAction::Goto(s1), GotoAction::Goto(s2)) => {
let group1 = group_ids_by_state_id[*s1];
let group2 = group_ids_by_state_id[*s2];
if group1 != group2 {
info!(
"split states {} {} - successors for {} are split: {s1} {s2}",
state1.id,
state2.id,
self.symbol_name(symbol),
);
return true;
}
}
_ => return true,
}
}
}
false
}
fn entries_conflict(
&self,
state_id1: ParseStateId,
state_id2: ParseStateId,
token: &Symbol,
entry1: &ParseTableEntry,
entry2: &ParseTableEntry,
group_ids_by_state_id: &[ParseStateId],
) -> bool {
// To be compatible, entries need to have the same actions.
let actions1 = &entry1.actions;
let actions2 = &entry2.actions;
if actions1.len() != actions2.len() {
info!(
"split states {state_id1} {state_id2} - differing action counts for token {}",
self.symbol_name(token)
);
return true;
}
for (i, action1) in actions1.iter().enumerate() {
let action2 = &actions2[i];
// Two shift actions are equivalent if their destinations are in the same group.
if let (
ParseAction::Shift {
state: s1,
is_repetition: is_repetition1,
},
ParseAction::Shift {
state: s2,
is_repetition: is_repetition2,
},
) = (action1, action2)
{
let group1 = group_ids_by_state_id[*s1];
let group2 = group_ids_by_state_id[*s2];
if group1 == group2 && is_repetition1 == is_repetition2 {
continue;
}
info!(
"split states {state_id1} {state_id2} - successors for {} are split: {s1} {s2}",
self.symbol_name(token),
);
return true;
} else if action1 != action2 {
info!(
"split states {state_id1} {state_id2} - unequal actions for {}",
self.symbol_name(token),
);
return true;
}
}
false
}
fn token_conflicts(
&self,
left_id: ParseStateId,
right_id: ParseStateId,
right_state: &ParseState,
new_token: Symbol,
) -> bool {
if new_token == Symbol::end_of_nonterminal_extra() {
info!("split states {left_id} {right_id} - end of non-terminal extra",);
return true;
}
// Do not add external tokens; they could conflict lexically with any of the state's
// existing lookahead tokens.
if new_token.is_external() {
info!(
"split states {left_id} {right_id} - external token {}",
self.symbol_name(&new_token),
);
return true;
}
if right_state.reserved_words.contains(&new_token) {
return false;
}
// Do not add tokens which are both internal and external. Their validity could
// influence the behavior of the external scanner.
if self
.syntax_grammar
.external_tokens
.iter()
.any(|external| external.corresponding_internal_token == Some(new_token))
{
info!(
"split states {left_id} {right_id} - internal/external token {}",
self.symbol_name(&new_token),
);
return true;
}
// Do not add a token if it conflicts with an existing token.
for token in right_state.terminal_entries.keys().copied() {
if !token.is_terminal() {
continue;
}
if self.syntax_grammar.word_token == Some(token) && self.keywords.contains(&new_token) {
continue;
}
if self.syntax_grammar.word_token == Some(new_token) && self.keywords.contains(&token) {
continue;
}
if self
.token_conflict_map
.does_conflict(new_token.index, token.index)
|| self
.token_conflict_map
.does_match_same_string(new_token.index, token.index)
{
info!(
"split states {} {} - token {} conflicts with {}",
left_id,
right_id,
self.symbol_name(&new_token),
self.symbol_name(&token),
);
return true;
}
}
false
}
fn symbol_name(&self, symbol: &Symbol) -> &String {
if symbol.is_non_terminal() {
&self.syntax_grammar.variables[symbol.index].name
} else if symbol.is_external() {
&self.syntax_grammar.external_tokens[symbol.index].name
} else {
&self.lexical_grammar.variables[symbol.index].name
}
}
fn remove_unused_states(&mut self) {
let mut state_usage_map = vec![false; self.parse_table.states.len()];
state_usage_map[0] = true;
state_usage_map[1] = true;
for state in &self.parse_table.states {
for referenced_state in state.referenced_states() {
state_usage_map[referenced_state] = true;
}
}
let mut removed_predecessor_count = 0;
let mut state_replacement_map = vec![0; self.parse_table.states.len()];
for state_id in 0..self.parse_table.states.len() {
state_replacement_map[state_id] = state_id - removed_predecessor_count;
if !state_usage_map[state_id] {
removed_predecessor_count += 1;
}
}
let mut state_id = 0;
let mut original_state_id = 0;
while state_id < self.parse_table.states.len() {
if state_usage_map[original_state_id] {
self.parse_table.states[state_id].update_referenced_states(|other_state_id, _| {
state_replacement_map[other_state_id]
});
state_id += 1;
} else {
self.parse_table.states.remove(state_id);
}
original_state_id += 1;
}
}
fn reorder_states_by_descending_size(&mut self) {
// Get a mapping of old state index -> new_state_index
let mut old_ids_by_new_id = (0..self.parse_table.states.len()).collect::<Vec<_>>();
old_ids_by_new_id.sort_unstable_by_key(|i| {
// Don't changes states 0 (the error state) or 1 (the start state).
if *i <= 1 {
return *i as i64 - 1_000_000;
}
// Reorder all the other states by descending symbol count.
let state = &self.parse_table.states[*i];
-((state.terminal_entries.len() + state.nonterminal_entries.len()) as i64)
});
// Get the inverse mapping
let mut new_ids_by_old_id = vec![0; old_ids_by_new_id.len()];
for (id, old_id) in old_ids_by_new_id.iter().enumerate() {
new_ids_by_old_id[*old_id] = id;
}
// Reorder the parse states and update their references to reflect
// the new ordering.
self.parse_table.states = old_ids_by_new_id
.iter()
.map(|old_id| {
let mut state = ParseState::default();
mem::swap(&mut state, &mut self.parse_table.states[*old_id]);
state.update_referenced_states(|id, _| new_ids_by_old_id[id]);
state
})
.collect();
}
}

View file

@ -1,529 +0,0 @@
use std::{cmp::Ordering, collections::HashSet, fmt};
use crate::{
build_tables::item::TokenSetDisplay,
grammars::{LexicalGrammar, SyntaxGrammar},
nfa::{CharacterSet, NfaCursor, NfaTransition},
rules::TokenSet,
};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
struct TokenConflictStatus {
matches_prefix: bool,
does_match_continuation: bool,
does_match_valid_continuation: bool,
does_match_separators: bool,
matches_same_string: bool,
matches_different_string: bool,
}
pub struct TokenConflictMap<'a> {
n: usize,
status_matrix: Vec<TokenConflictStatus>,
following_tokens: Vec<TokenSet>,
starting_chars_by_index: Vec<CharacterSet>,
following_chars_by_index: Vec<CharacterSet>,
grammar: &'a LexicalGrammar,
}
impl<'a> TokenConflictMap<'a> {
/// Create a token conflict map based on a lexical grammar, which describes the structure
/// each token, and a `following_token` map, which indicates which tokens may be appear
/// immediately after each other token.
///
/// This analyzes the possible kinds of overlap between each pair of tokens and stores
/// them in a matrix.
pub fn new(grammar: &'a LexicalGrammar, following_tokens: Vec<TokenSet>) -> Self {
let mut cursor = NfaCursor::new(&grammar.nfa, Vec::new());
let starting_chars = get_starting_chars(&mut cursor, grammar);
let following_chars = get_following_chars(&starting_chars, &following_tokens);
let n = grammar.variables.len();
let mut status_matrix = vec![TokenConflictStatus::default(); n * n];
for i in 0..grammar.variables.len() {
for j in 0..i {
let status = compute_conflict_status(&mut cursor, grammar, &following_chars, i, j);
status_matrix[matrix_index(n, i, j)] = status.0;
status_matrix[matrix_index(n, j, i)] = status.1;
}
}
TokenConflictMap {
n,
status_matrix,
following_tokens,
starting_chars_by_index: starting_chars,
following_chars_by_index: following_chars,
grammar,
}
}
/// Does token `i` match any strings that token `j` also matches, such that token `i`
/// is preferred over token `j`?
pub fn has_same_conflict_status(&self, a: usize, b: usize, other: usize) -> bool {
let left = &self.status_matrix[matrix_index(self.n, a, other)];
let right = &self.status_matrix[matrix_index(self.n, b, other)];
left == right
}
/// Does token `i` match any strings that token `j` does *not* match?
pub fn does_match_different_string(&self, i: usize, j: usize) -> bool {
self.status_matrix[matrix_index(self.n, i, j)].matches_different_string
}
/// Does token `i` match any strings that token `j` also matches, where
/// token `i` is preferred over token `j`?
pub fn does_match_same_string(&self, i: usize, j: usize) -> bool {
self.status_matrix[matrix_index(self.n, i, j)].matches_same_string
}
pub fn does_conflict(&self, i: usize, j: usize) -> bool {
let entry = &self.status_matrix[matrix_index(self.n, i, j)];
entry.does_match_valid_continuation
|| entry.does_match_separators
|| entry.matches_same_string
}
/// Does token `i` match any strings that are *prefixes* of strings matched by `j`?
pub fn does_match_prefix(&self, i: usize, j: usize) -> bool {
self.status_matrix[matrix_index(self.n, i, j)].matches_prefix
}
pub fn does_match_shorter_or_longer(&self, i: usize, j: usize) -> bool {
let entry = &self.status_matrix[matrix_index(self.n, i, j)];
let reverse_entry = &self.status_matrix[matrix_index(self.n, j, i)];
(entry.does_match_valid_continuation || entry.does_match_separators)
&& !reverse_entry.does_match_separators
}
pub fn does_overlap(&self, i: usize, j: usize) -> bool {
let status = &self.status_matrix[matrix_index(self.n, i, j)];
status.does_match_separators
|| status.matches_prefix
|| status.matches_same_string
|| status.does_match_continuation
}
pub fn prefer_token(grammar: &LexicalGrammar, left: (i32, usize), right: (i32, usize)) -> bool {
match left.0.cmp(&right.0) {
Ordering::Less => false,
Ordering::Greater => true,
Ordering::Equal => match grammar.variables[left.1]
.implicit_precedence
.cmp(&grammar.variables[right.1].implicit_precedence)
{
Ordering::Less => false,
Ordering::Greater => true,
Ordering::Equal => left.1 < right.1,
},
}
}
pub fn prefer_transition(
grammar: &LexicalGrammar,
t: &NfaTransition,
completed_id: usize,
completed_precedence: i32,
has_separator_transitions: bool,
) -> bool {
if t.precedence < completed_precedence {
return false;
}
if t.precedence == completed_precedence {
if t.is_separator {
return false;
}
if has_separator_transitions
&& !grammar
.variable_indices_for_nfa_states(&t.states)
.any(|i| i == completed_id)
{
return false;
}
}
true
}
}
impl fmt::Debug for TokenConflictMap<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "TokenConflictMap {{")?;
let syntax_grammar = SyntaxGrammar::default();
writeln!(f, " following_tokens: {{")?;
for (i, following_tokens) in self.following_tokens.iter().enumerate() {
writeln!(
f,
" follow({:?}): {},",
self.grammar.variables[i].name,
TokenSetDisplay(following_tokens, &syntax_grammar, self.grammar)
)?;
}
writeln!(f, " }},")?;
writeln!(f, " starting_characters: {{")?;
for i in 0..self.n {
writeln!(
f,
" {:?}: {:?},",
self.grammar.variables[i].name, self.starting_chars_by_index[i]
)?;
}
writeln!(f, " }},")?;
writeln!(f, " following_characters: {{")?;
for i in 0..self.n {
writeln!(
f,
" {:?}: {:?},",
self.grammar.variables[i].name, self.following_chars_by_index[i]
)?;
}
writeln!(f, " }},")?;
writeln!(f, " status_matrix: {{")?;
for i in 0..self.n {
writeln!(f, " {:?}: {{", self.grammar.variables[i].name)?;
for j in 0..self.n {
writeln!(
f,
" {:?}: {:?},",
self.grammar.variables[j].name,
self.status_matrix[matrix_index(self.n, i, j)]
)?;
}
writeln!(f, " }},")?;
}
write!(f, " }},")?;
write!(f, "}}")?;
Ok(())
}
}
const fn matrix_index(variable_count: usize, i: usize, j: usize) -> usize {
variable_count * i + j
}
fn get_starting_chars(cursor: &mut NfaCursor, grammar: &LexicalGrammar) -> Vec<CharacterSet> {
let mut result = Vec::with_capacity(grammar.variables.len());
for variable in &grammar.variables {
cursor.reset(vec![variable.start_state]);
let mut all_chars = CharacterSet::empty();
for (chars, _) in cursor.transition_chars() {
all_chars = all_chars.add(chars);
}
result.push(all_chars);
}
result
}
fn get_following_chars(
starting_chars: &[CharacterSet],
following_tokens: &[TokenSet],
) -> Vec<CharacterSet> {
following_tokens
.iter()
.map(|following_tokens| {
let mut chars = CharacterSet::empty();
for token in following_tokens.iter() {
if token.is_terminal() {
chars = chars.add(&starting_chars[token.index]);
}
}
chars
})
.collect()
}
fn compute_conflict_status(
cursor: &mut NfaCursor,
grammar: &LexicalGrammar,
following_chars: &[CharacterSet],
i: usize,
j: usize,
) -> (TokenConflictStatus, TokenConflictStatus) {
let mut visited_state_sets = HashSet::new();
let mut state_set_queue = vec![vec![
grammar.variables[i].start_state,
grammar.variables[j].start_state,
]];
let mut result = (
TokenConflictStatus::default(),
TokenConflictStatus::default(),
);
while let Some(state_set) = state_set_queue.pop() {
let mut live_variable_indices = grammar.variable_indices_for_nfa_states(&state_set);
// If only one of the two tokens could possibly match from this state, then
// there is no reason to analyze any of its successors. Just record the fact
// that the token matches a string that the other token does not match.
let first_live_variable_index = live_variable_indices.next().unwrap();
if live_variable_indices.count() == 0 {
if first_live_variable_index == i {
result.0.matches_different_string = true;
} else {
result.1.matches_different_string = true;
}
continue;
}
// Don't pursue states where there's no potential for conflict.
cursor.reset(state_set);
let within_separator = cursor.transition_chars().any(|(_, sep)| sep);
// Examine each possible completed token in this state.
let mut completion = None;
for (id, precedence) in cursor.completions() {
if within_separator {
if id == i {
result.0.does_match_separators = true;
} else {
result.1.does_match_separators = true;
}
}
// If the other token has already completed, then this is
// a same-string conflict.
if let Some((prev_id, prev_precedence)) = completion {
if id == prev_id {
continue;
}
// Determine which of the two tokens is preferred.
let preferred_id;
if TokenConflictMap::prefer_token(
grammar,
(prev_precedence, prev_id),
(precedence, id),
) {
preferred_id = prev_id;
} else {
preferred_id = id;
completion = Some((id, precedence));
}
if preferred_id == i {
result.0.matches_same_string = true;
} else {
result.1.matches_same_string = true;
}
} else {
completion = Some((id, precedence));
}
}
// Examine each possible transition from this state to detect substring conflicts.
for transition in cursor.transitions() {
let mut can_advance = true;
// If there is already a completed token in this state, then determine
// if the next state can also match the completed token. If so, then
// this is *not* a conflict.
if let Some((completed_id, completed_precedence)) = completion {
let mut advanced_id = None;
let mut successor_contains_completed_id = false;
for variable_id in grammar.variable_indices_for_nfa_states(&transition.states) {
if variable_id == completed_id {
successor_contains_completed_id = true;
break;
}
advanced_id = Some(variable_id);
}
// Determine which action is preferred: matching the already complete
// token, or continuing on to try and match the other longer token.
if let (Some(advanced_id), false) = (advanced_id, successor_contains_completed_id) {
if TokenConflictMap::prefer_transition(
grammar,
&transition,
completed_id,
completed_precedence,
within_separator,
) {
can_advance = true;
if advanced_id == i {
result.0.does_match_continuation = true;
if transition.characters.does_intersect(&following_chars[j]) {
result.0.does_match_valid_continuation = true;
}
} else {
result.1.does_match_continuation = true;
if transition.characters.does_intersect(&following_chars[i]) {
result.1.does_match_valid_continuation = true;
}
}
} else if completed_id == i {
result.0.matches_prefix = true;
} else {
result.1.matches_prefix = true;
}
}
}
if can_advance && visited_state_sets.insert(transition.states.clone()) {
state_set_queue.push(transition.states);
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
grammars::{Variable, VariableType},
prepare_grammar::{expand_tokens, ExtractedLexicalGrammar},
rules::{Precedence, Rule, Symbol},
};
#[test]
fn test_starting_characters() {
let grammar = expand_tokens(ExtractedLexicalGrammar {
separators: Vec::new(),
variables: vec![
Variable {
name: "token_0".to_string(),
kind: VariableType::Named,
rule: Rule::pattern("[a-f]1|0x\\d", ""),
},
Variable {
name: "token_1".to_string(),
kind: VariableType::Named,
rule: Rule::pattern("d*ef", ""),
},
],
})
.unwrap();
let token_map = TokenConflictMap::new(&grammar, Vec::new());
assert_eq!(
token_map.starting_chars_by_index[0],
CharacterSet::empty().add_range('a', 'f').add_char('0')
);
assert_eq!(
token_map.starting_chars_by_index[1],
CharacterSet::empty().add_range('d', 'e')
);
}
#[test]
fn test_token_conflicts() {
let grammar = expand_tokens(ExtractedLexicalGrammar {
separators: Vec::new(),
variables: vec![
Variable {
name: "in".to_string(),
kind: VariableType::Named,
rule: Rule::string("in"),
},
Variable {
name: "identifier".to_string(),
kind: VariableType::Named,
rule: Rule::pattern("\\w+", ""),
},
Variable {
name: "instanceof".to_string(),
kind: VariableType::Named,
rule: Rule::string("instanceof"),
},
],
})
.unwrap();
let var = |name| index_of_var(&grammar, name);
let token_map = TokenConflictMap::new(
&grammar,
vec![
std::iter::once(&Symbol::terminal(var("identifier")))
.copied()
.collect(),
std::iter::once(&Symbol::terminal(var("in")))
.copied()
.collect(),
std::iter::once(&Symbol::terminal(var("identifier")))
.copied()
.collect(),
],
);
// Given the string "in", the `in` token is preferred over the `identifier` token
assert!(token_map.does_match_same_string(var("in"), var("identifier")));
assert!(!token_map.does_match_same_string(var("identifier"), var("in")));
// Depending on what character follows, the string "in" may be treated as part of an
// `identifier` token.
assert!(token_map.does_conflict(var("identifier"), var("in")));
// Depending on what character follows, the string "instanceof" may be treated as part of
// an `identifier` token.
assert!(token_map.does_conflict(var("identifier"), var("instanceof")));
assert!(token_map.does_conflict(var("instanceof"), var("in")));
}
#[test]
fn test_token_conflicts_with_separators() {
let grammar = expand_tokens(ExtractedLexicalGrammar {
separators: vec![Rule::pattern("\\s", "")],
variables: vec![
Variable {
name: "x".to_string(),
kind: VariableType::Named,
rule: Rule::string("x"),
},
Variable {
name: "newline".to_string(),
kind: VariableType::Named,
rule: Rule::string("\n"),
},
],
})
.unwrap();
let var = |name| index_of_var(&grammar, name);
let token_map = TokenConflictMap::new(&grammar, vec![TokenSet::new(); 4]);
assert!(token_map.does_conflict(var("newline"), var("x")));
assert!(!token_map.does_conflict(var("x"), var("newline")));
}
#[test]
fn test_token_conflicts_with_open_ended_tokens() {
let grammar = expand_tokens(ExtractedLexicalGrammar {
separators: vec![Rule::pattern("\\s", "")],
variables: vec![
Variable {
name: "x".to_string(),
kind: VariableType::Named,
rule: Rule::string("x"),
},
Variable {
name: "anything".to_string(),
kind: VariableType::Named,
rule: Rule::prec(Precedence::Integer(-1), Rule::pattern(".*", "")),
},
],
})
.unwrap();
let var = |name| index_of_var(&grammar, name);
let token_map = TokenConflictMap::new(&grammar, vec![TokenSet::new(); 4]);
assert!(token_map.does_match_shorter_or_longer(var("anything"), var("x")));
assert!(!token_map.does_match_shorter_or_longer(var("x"), var("anything")));
}
fn index_of_var(grammar: &LexicalGrammar, name: &str) -> usize {
grammar
.variables
.iter()
.position(|v| v.name == name)
.unwrap()
}
}

View file

@ -1 +0,0 @@

View file

@ -1,273 +0,0 @@
use std::{collections::HashMap, fmt};
use super::{
nfa::Nfa,
rules::{Alias, Associativity, Precedence, Rule, Symbol, TokenSet},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum VariableType {
Hidden,
Auxiliary,
Anonymous,
Named,
}
// Input grammar
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Variable {
pub name: String,
pub kind: VariableType,
pub rule: Rule,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PrecedenceEntry {
Name(String),
Symbol(String),
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct InputGrammar {
pub name: String,
pub variables: Vec<Variable>,
pub extra_symbols: Vec<Rule>,
pub expected_conflicts: Vec<Vec<String>>,
pub precedence_orderings: Vec<Vec<PrecedenceEntry>>,
pub external_tokens: Vec<Rule>,
pub variables_to_inline: Vec<String>,
pub supertype_symbols: Vec<String>,
pub word_token: Option<String>,
pub reserved_words: Vec<ReservedWordContext<Rule>>,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct ReservedWordContext<T> {
pub name: String,
pub reserved_words: Vec<T>,
}
// Extracted lexical grammar
#[derive(Debug, PartialEq, Eq)]
pub struct LexicalVariable {
pub name: String,
pub kind: VariableType,
pub implicit_precedence: i32,
pub start_state: u32,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct LexicalGrammar {
pub nfa: Nfa,
pub variables: Vec<LexicalVariable>,
}
// Extracted syntax grammar
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ProductionStep {
pub symbol: Symbol,
pub precedence: Precedence,
pub associativity: Option<Associativity>,
pub alias: Option<Alias>,
pub field_name: Option<String>,
pub reserved_word_set_id: ReservedWordSetId,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ReservedWordSetId(pub usize);
impl fmt::Display for ReservedWordSetId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
pub const NO_RESERVED_WORDS: ReservedWordSetId = ReservedWordSetId(usize::MAX);
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Production {
pub steps: Vec<ProductionStep>,
pub dynamic_precedence: i32,
}
#[derive(Default)]
pub struct InlinedProductionMap {
pub productions: Vec<Production>,
pub production_map: HashMap<(*const Production, u32), Vec<usize>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SyntaxVariable {
pub name: String,
pub kind: VariableType,
pub productions: Vec<Production>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ExternalToken {
pub name: String,
pub kind: VariableType,
pub corresponding_internal_token: Option<Symbol>,
}
#[derive(Debug, Default)]
pub struct SyntaxGrammar {
pub variables: Vec<SyntaxVariable>,
pub extra_symbols: Vec<Symbol>,
pub expected_conflicts: Vec<Vec<Symbol>>,
pub external_tokens: Vec<ExternalToken>,
pub supertype_symbols: Vec<Symbol>,
pub variables_to_inline: Vec<Symbol>,
pub word_token: Option<Symbol>,
pub precedence_orderings: Vec<Vec<PrecedenceEntry>>,
pub reserved_word_sets: Vec<TokenSet>,
}
#[cfg(test)]
impl ProductionStep {
#[must_use]
pub fn new(symbol: Symbol) -> Self {
Self {
symbol,
precedence: Precedence::None,
associativity: None,
alias: None,
field_name: None,
reserved_word_set_id: ReservedWordSetId::default(),
}
}
pub fn with_prec(
mut self,
precedence: Precedence,
associativity: Option<Associativity>,
) -> Self {
self.precedence = precedence;
self.associativity = associativity;
self
}
pub fn with_alias(mut self, value: &str, is_named: bool) -> Self {
self.alias = Some(Alias {
value: value.to_string(),
is_named,
});
self
}
pub fn with_field_name(mut self, name: &str) -> Self {
self.field_name = Some(name.to_string());
self
}
}
impl Production {
pub fn first_symbol(&self) -> Option<Symbol> {
self.steps.first().map(|s| s.symbol)
}
}
#[cfg(test)]
impl Variable {
pub fn named(name: &str, rule: Rule) -> Self {
Self {
name: name.to_string(),
kind: VariableType::Named,
rule,
}
}
pub fn auxiliary(name: &str, rule: Rule) -> Self {
Self {
name: name.to_string(),
kind: VariableType::Auxiliary,
rule,
}
}
pub fn hidden(name: &str, rule: Rule) -> Self {
Self {
name: name.to_string(),
kind: VariableType::Hidden,
rule,
}
}
pub fn anonymous(name: &str, rule: Rule) -> Self {
Self {
name: name.to_string(),
kind: VariableType::Anonymous,
rule,
}
}
}
impl VariableType {
pub fn is_visible(self) -> bool {
self == Self::Named || self == Self::Anonymous
}
}
impl LexicalGrammar {
pub fn variable_indices_for_nfa_states<'a>(
&'a self,
state_ids: &'a [u32],
) -> impl Iterator<Item = usize> + 'a {
let mut prev = None;
state_ids.iter().filter_map(move |state_id| {
let variable_id = self.variable_index_for_nfa_state(*state_id);
if prev == Some(variable_id) {
None
} else {
prev = Some(variable_id);
prev
}
})
}
pub fn variable_index_for_nfa_state(&self, state_id: u32) -> usize {
self.variables
.iter()
.position(|v| v.start_state >= state_id)
.unwrap()
}
}
impl SyntaxVariable {
pub fn is_auxiliary(&self) -> bool {
self.kind == VariableType::Auxiliary
}
pub fn is_hidden(&self) -> bool {
self.kind == VariableType::Hidden || self.kind == VariableType::Auxiliary
}
}
impl InlinedProductionMap {
pub fn inlined_productions<'a>(
&'a self,
production: &Production,
step_index: u32,
) -> Option<impl Iterator<Item = &'a Production> + 'a> {
self.production_map
.get(&(std::ptr::from_ref::<Production>(production), step_index))
.map(|production_indices| {
production_indices
.iter()
.copied()
.map(move |index| &self.productions[index])
})
}
}
impl fmt::Display for PrecedenceEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Name(n) => write!(f, "'{n}'"),
Self::Symbol(s) => write!(f, "$.{s}"),
}
}
}

View file

@ -1,439 +0,0 @@
use std::{
env, fs,
io::Write,
path::{Path, PathBuf},
process::{Command, Stdio},
sync::LazyLock,
};
use anyhow::Result;
use regex::{Regex, RegexBuilder};
use semver::Version;
use serde::{Deserialize, Serialize};
use thiserror::Error;
mod build_tables;
mod dedup;
mod grammar_files;
mod grammars;
mod nfa;
mod node_types;
pub mod parse_grammar;
mod prepare_grammar;
mod render;
mod rules;
mod tables;
use build_tables::build_tables;
pub use build_tables::ParseTableBuilderError;
use grammars::InputGrammar;
pub use node_types::VariableInfoError;
use parse_grammar::parse_grammar;
pub use parse_grammar::ParseGrammarError;
use prepare_grammar::prepare_grammar;
pub use prepare_grammar::PrepareGrammarError;
use render::render_c_code;
pub use render::{ABI_VERSION_MAX, ABI_VERSION_MIN};
static JSON_COMMENT_REGEX: LazyLock<Regex> = LazyLock::new(|| {
RegexBuilder::new("^\\s*//.*")
.multi_line(true)
.build()
.unwrap()
});
struct GeneratedParser {
c_code: String,
node_types_json: String,
}
pub const ALLOC_HEADER: &str = include_str!("templates/alloc.h");
pub const ARRAY_HEADER: &str = include_str!("templates/array.h");
pub type GenerateResult<T> = Result<T, GenerateError>;
#[derive(Debug, Error, Serialize)]
pub enum GenerateError {
#[error("Error with specified path -- {0}")]
GrammarPath(String),
#[error("{0}")]
IO(String),
#[error(transparent)]
LoadGrammarFile(#[from] LoadGrammarError),
#[error(transparent)]
ParseGrammar(#[from] ParseGrammarError),
#[error(transparent)]
Prepare(#[from] PrepareGrammarError),
#[error(transparent)]
VariableInfo(#[from] VariableInfoError),
#[error(transparent)]
BuildTables(#[from] ParseTableBuilderError),
#[error(transparent)]
ParseVersion(#[from] ParseVersionError),
}
impl From<std::io::Error> for GenerateError {
fn from(value: std::io::Error) -> Self {
Self::IO(value.to_string())
}
}
pub type LoadGrammarFileResult<T> = Result<T, LoadGrammarError>;
#[derive(Debug, Error, Serialize)]
pub enum LoadGrammarError {
#[error("Path to a grammar file with `.js` or `.json` extension is required")]
InvalidPath,
#[error("Failed to load grammar.js -- {0}")]
LoadJSGrammarFile(#[from] JSError),
#[error("Failed to load grammar.json -- {0}")]
IO(String),
#[error("Unknown grammar file extension: {0:?}")]
FileExtension(PathBuf),
}
impl From<std::io::Error> for LoadGrammarError {
fn from(value: std::io::Error) -> Self {
Self::IO(value.to_string())
}
}
#[derive(Debug, Error, Serialize)]
pub enum ParseVersionError {
#[error("{0}")]
Version(String),
#[error("{0}")]
JSON(String),
#[error("{0}")]
IO(String),
}
pub type JSResult<T> = Result<T, JSError>;
#[derive(Debug, Error, Serialize)]
pub enum JSError {
#[error("Failed to run `{runtime}` -- {error}")]
JSRuntimeSpawn { runtime: String, error: String },
#[error("Got invalid UTF8 from `{runtime}` -- {error}")]
JSRuntimeUtf8 { runtime: String, error: String },
#[error("`{runtime}` process exited with status {code}")]
JSRuntimeExit { runtime: String, code: i32 },
#[error("{0}")]
IO(String),
#[error("Could not parse this package's version as semver -- {0}")]
Semver(String),
#[error("Failed to serialze grammar JSON -- {0}")]
Serialzation(String),
}
impl From<std::io::Error> for JSError {
fn from(value: std::io::Error) -> Self {
Self::IO(value.to_string())
}
}
impl From<serde_json::Error> for JSError {
fn from(value: serde_json::Error) -> Self {
Self::Serialzation(value.to_string())
}
}
impl From<semver::Error> for JSError {
fn from(value: semver::Error) -> Self {
Self::Semver(value.to_string())
}
}
pub fn generate_parser_in_directory(
repo_path: &Path,
out_path: Option<&str>,
grammar_path: Option<&str>,
mut abi_version: usize,
report_symbol_name: Option<&str>,
js_runtime: Option<&str>,
) -> GenerateResult<()> {
let mut repo_path = repo_path.to_owned();
let mut grammar_path = grammar_path;
// Populate a new empty grammar directory.
if let Some(path) = grammar_path {
let path = PathBuf::from(path);
if !path
.try_exists()
.map_err(|e| GenerateError::GrammarPath(e.to_string()))?
{
fs::create_dir_all(&path)?;
grammar_path = None;
repo_path = path;
}
}
let grammar_path = grammar_path.map_or_else(|| repo_path.join("grammar.js"), PathBuf::from);
// Read the grammar file.
let grammar_json = load_grammar_file(&grammar_path, js_runtime)?;
let src_path = out_path.map_or_else(|| repo_path.join("src"), PathBuf::from);
let header_path = src_path.join("tree_sitter");
// Ensure that the output directories exist.
fs::create_dir_all(&src_path)?;
fs::create_dir_all(&header_path)?;
if grammar_path.file_name().unwrap() != "grammar.json" {
fs::write(src_path.join("grammar.json"), &grammar_json).map_err(|e| {
GenerateError::IO(format!(
"Failed to write grammar.json to {src_path:?} -- {e}"
))
})?;
}
// Parse and preprocess the grammar.
let input_grammar = parse_grammar(&grammar_json)?;
let semantic_version = read_grammar_version(&repo_path)?;
if semantic_version.is_none() && abi_version > ABI_VERSION_MIN {
println!("Warning: No `tree-sitter.json` file found in your grammar, this file is required to generate with ABI {abi_version}. Using ABI version {ABI_VERSION_MIN} instead.");
println!("This file can be set up with `tree-sitter init`. For more information, see https://tree-sitter.github.io/tree-sitter/cli/init.");
abi_version = ABI_VERSION_MIN;
}
// Generate the parser and related files.
let GeneratedParser {
c_code,
node_types_json,
} = generate_parser_for_grammar_with_opts(
&input_grammar,
abi_version,
semantic_version.map(|v| (v.major as u8, v.minor as u8, v.patch as u8)),
report_symbol_name,
)?;
write_file(&src_path.join("parser.c"), c_code)?;
write_file(&src_path.join("node-types.json"), node_types_json)?;
write_file(&header_path.join("alloc.h"), ALLOC_HEADER)?;
write_file(&header_path.join("array.h"), ARRAY_HEADER)?;
write_file(&header_path.join("parser.h"), tree_sitter::PARSER_HEADER)?;
Ok(())
}
pub fn generate_parser_for_grammar(
grammar_json: &str,
semantic_version: Option<(u8, u8, u8)>,
) -> GenerateResult<(String, String)> {
let grammar_json = JSON_COMMENT_REGEX.replace_all(grammar_json, "\n");
let input_grammar = parse_grammar(&grammar_json)?;
let parser = generate_parser_for_grammar_with_opts(
&input_grammar,
tree_sitter::LANGUAGE_VERSION,
semantic_version,
None,
)?;
Ok((input_grammar.name, parser.c_code))
}
fn generate_parser_for_grammar_with_opts(
input_grammar: &InputGrammar,
abi_version: usize,
semantic_version: Option<(u8, u8, u8)>,
report_symbol_name: Option<&str>,
) -> GenerateResult<GeneratedParser> {
let (syntax_grammar, lexical_grammar, inlines, simple_aliases) =
prepare_grammar(input_grammar)?;
let variable_info =
node_types::get_variable_info(&syntax_grammar, &lexical_grammar, &simple_aliases)?;
let node_types_json = node_types::generate_node_types_json(
&syntax_grammar,
&lexical_grammar,
&simple_aliases,
&variable_info,
);
let supertype_symbol_map =
node_types::get_supertype_symbol_map(&syntax_grammar, &simple_aliases, &variable_info);
let tables = build_tables(
&syntax_grammar,
&lexical_grammar,
&simple_aliases,
&variable_info,
&inlines,
report_symbol_name,
)?;
let c_code = render_c_code(
&input_grammar.name,
tables,
syntax_grammar,
lexical_grammar,
simple_aliases,
abi_version,
semantic_version,
supertype_symbol_map,
);
Ok(GeneratedParser {
c_code,
node_types_json: serde_json::to_string_pretty(&node_types_json).unwrap(),
})
}
/// This will read the `tree-sitter.json` config file and attempt to extract the version.
///
/// If the file is not found in the current directory or any of its parent directories, this will
/// return `None` to maintain backwards compatibility. If the file is found but the version cannot
/// be parsed as semver, this will return an error.
fn read_grammar_version(repo_path: &Path) -> Result<Option<Version>, ParseVersionError> {
#[derive(Deserialize)]
struct TreeSitterJson {
metadata: Metadata,
}
#[derive(Deserialize)]
struct Metadata {
version: String,
}
let filename = "tree-sitter.json";
let mut path = repo_path.join(filename);
loop {
let json = path
.exists()
.then(|| {
let contents = fs::read_to_string(path.as_path()).map_err(|e| {
ParseVersionError::IO(format!("Failed to read `{}` -- {e}", path.display()))
})?;
serde_json::from_str::<TreeSitterJson>(&contents).map_err(|e| {
ParseVersionError::JSON(format!("Failed to parse `{}` -- {e}", path.display()))
})
})
.transpose()?;
if let Some(json) = json {
return Version::parse(&json.metadata.version)
.map_err(|e| {
ParseVersionError::Version(format!(
"Failed to parse `{}` version as semver -- {e}",
path.display()
))
})
.map(Some);
}
path.pop(); // filename
if !path.pop() {
return Ok(None);
}
path.push(filename);
}
}
pub fn load_grammar_file(
grammar_path: &Path,
js_runtime: Option<&str>,
) -> LoadGrammarFileResult<String> {
if grammar_path.is_dir() {
Err(LoadGrammarError::InvalidPath)?;
}
match grammar_path.extension().and_then(|e| e.to_str()) {
Some("js") => Ok(load_js_grammar_file(grammar_path, js_runtime)?),
Some("json") => Ok(fs::read_to_string(grammar_path)?),
_ => Err(LoadGrammarError::FileExtension(grammar_path.to_owned()))?,
}
}
fn load_js_grammar_file(grammar_path: &Path, js_runtime: Option<&str>) -> JSResult<String> {
let grammar_path = fs::canonicalize(grammar_path)?;
#[cfg(windows)]
let grammar_path = url::Url::from_file_path(grammar_path)
.expect("Failed to convert path to URL")
.to_string();
let js_runtime = js_runtime.unwrap_or("node");
let mut js_command = Command::new(js_runtime);
match js_runtime {
"node" => {
js_command.args(["--input-type=module", "-"]);
}
"bun" => {
js_command.arg("-");
}
"deno" => {
js_command.args(["run", "--allow-all", "-"]);
}
_ => {}
}
let mut js_process = js_command
.env("TREE_SITTER_GRAMMAR_PATH", grammar_path)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.map_err(|e| JSError::JSRuntimeSpawn {
runtime: js_runtime.to_string(),
error: e.to_string(),
})?;
let mut js_stdin = js_process
.stdin
.take()
.ok_or_else(|| JSError::IO(format!("Failed to open stdin for `{js_runtime}`")))?;
let cli_version = Version::parse(env!("CARGO_PKG_VERSION"))?;
write!(
js_stdin,
"globalThis.TREE_SITTER_CLI_VERSION_MAJOR = {};
globalThis.TREE_SITTER_CLI_VERSION_MINOR = {};
globalThis.TREE_SITTER_CLI_VERSION_PATCH = {};",
cli_version.major, cli_version.minor, cli_version.patch,
)
.map_err(|e| {
JSError::IO(format!(
"Failed to write tree-sitter version to `{js_runtime}`'s stdin -- {e}"
))
})?;
js_stdin.write(include_bytes!("./dsl.js")).map_err(|e| {
JSError::IO(format!(
"Failed to write grammar dsl to `{js_runtime}`'s stdin -- {e}"
))
})?;
drop(js_stdin);
let output = js_process
.wait_with_output()
.map_err(|e| JSError::IO(format!("Failed to read output from `{js_runtime}` -- {e}")))?;
match output.status.code() {
None => panic!("`{js_runtime}` process was killed"),
Some(0) => {
let stdout = String::from_utf8(output.stdout).map_err(|e| JSError::JSRuntimeUtf8 {
runtime: js_runtime.to_string(),
error: e.to_string(),
})?;
let mut grammar_json = &stdout[..];
if let Some(pos) = stdout.rfind('\n') {
// If there's a newline, split the last line from the rest of the output
let node_output = &stdout[..pos];
grammar_json = &stdout[pos + 1..];
let mut stdout = std::io::stdout().lock();
stdout.write_all(node_output.as_bytes())?;
stdout.write_all(b"\n")?;
stdout.flush()?;
}
Ok(serde_json::to_string_pretty(&serde_json::from_str::<
serde_json::Value,
>(grammar_json)?)?)
}
Some(code) => Err(JSError::JSRuntimeExit {
runtime: js_runtime.to_string(),
code,
}),
}
}
pub fn write_file(path: &Path, body: impl AsRef<[u8]>) -> GenerateResult<()> {
fs::write(path, body)
.map_err(|e| GenerateError::IO(format!("Failed to write {:?} -- {e}", path.file_name())))
}

File diff suppressed because it is too large Load diff

View file

@ -1,433 +0,0 @@
use std::collections::HashSet;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use thiserror::Error;
use super::{
grammars::{InputGrammar, PrecedenceEntry, Variable, VariableType},
rules::{Precedence, Rule},
};
use crate::grammars::ReservedWordContext;
#[derive(Deserialize)]
#[serde(tag = "type")]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
enum RuleJSON {
ALIAS {
content: Box<RuleJSON>,
named: bool,
value: String,
},
BLANK,
STRING {
value: String,
},
PATTERN {
value: String,
flags: Option<String>,
},
SYMBOL {
name: String,
},
CHOICE {
members: Vec<RuleJSON>,
},
FIELD {
name: String,
content: Box<RuleJSON>,
},
SEQ {
members: Vec<RuleJSON>,
},
REPEAT {
content: Box<RuleJSON>,
},
REPEAT1 {
content: Box<RuleJSON>,
},
PREC_DYNAMIC {
value: i32,
content: Box<RuleJSON>,
},
PREC_LEFT {
value: PrecedenceValueJSON,
content: Box<RuleJSON>,
},
PREC_RIGHT {
value: PrecedenceValueJSON,
content: Box<RuleJSON>,
},
PREC {
value: PrecedenceValueJSON,
content: Box<RuleJSON>,
},
TOKEN {
content: Box<RuleJSON>,
},
IMMEDIATE_TOKEN {
content: Box<RuleJSON>,
},
RESERVED {
context_name: String,
content: Box<RuleJSON>,
},
}
#[derive(Deserialize)]
#[serde(untagged)]
enum PrecedenceValueJSON {
Integer(i32),
Name(String),
}
#[derive(Deserialize)]
pub struct GrammarJSON {
pub name: String,
rules: Map<String, Value>,
#[serde(default)]
precedences: Vec<Vec<RuleJSON>>,
#[serde(default)]
conflicts: Vec<Vec<String>>,
#[serde(default)]
externals: Vec<RuleJSON>,
#[serde(default)]
extras: Vec<RuleJSON>,
#[serde(default)]
inline: Vec<String>,
#[serde(default)]
supertypes: Vec<String>,
#[serde(default)]
word: Option<String>,
#[serde(default)]
reserved: Map<String, Value>,
}
pub type ParseGrammarResult<T> = Result<T, ParseGrammarError>;
#[derive(Debug, Error, Serialize)]
pub enum ParseGrammarError {
#[error("{0}")]
Serialization(String),
#[error("Rules in the `extras` array must not contain empty strings")]
InvalidExtra,
#[error("Invalid rule in precedences array. Only strings and symbols are allowed")]
Unexpected,
#[error("Reserved word sets must be arrays")]
InvalidReservedWordSet,
#[error("Grammar Error: Unexpected rule `{0}` in `token()` call")]
UnexpectedRule(String),
}
impl From<serde_json::Error> for ParseGrammarError {
fn from(value: serde_json::Error) -> Self {
Self::Serialization(value.to_string())
}
}
/// Check if a rule is referenced by another rule.
///
/// This function is used to determine if a variable is used in a given rule,
/// and `is_other` indicates if the rule is an external, and if it is,
/// to not assume that a named symbol that is equal to itself means it's being referenced.
///
/// For example, if we have an external rule **and** a normal rule both called `foo`,
/// `foo` should not be thought of as directly used unless it's used within another rule.
fn rule_is_referenced(rule: &Rule, target: &str, is_external: bool) -> bool {
match rule {
Rule::NamedSymbol(name) => name == target && !is_external,
Rule::Choice(rules) | Rule::Seq(rules) => {
rules.iter().any(|r| rule_is_referenced(r, target, false))
}
Rule::Metadata { rule, .. } | Rule::Reserved { rule, .. } => {
rule_is_referenced(rule, target, is_external)
}
Rule::Repeat(inner) => rule_is_referenced(inner, target, false),
Rule::Blank | Rule::String(_) | Rule::Pattern(_, _) | Rule::Symbol(_) => false,
}
}
fn variable_is_used(
grammar_rules: &[(String, Rule)],
extras: &[Rule],
externals: &[Rule],
target_name: &str,
in_progress: &mut HashSet<String>,
) -> bool {
let root = &grammar_rules.first().unwrap().0;
if target_name == root {
return true;
}
if extras
.iter()
.any(|rule| rule_is_referenced(rule, target_name, false))
{
return true;
}
if externals
.iter()
.any(|rule| rule_is_referenced(rule, target_name, true))
{
return true;
}
in_progress.insert(target_name.to_string());
let result = grammar_rules
.iter()
.filter(|(key, _)| *key != target_name)
.any(|(name, rule)| {
if !rule_is_referenced(rule, target_name, false) || in_progress.contains(name) {
return false;
}
variable_is_used(grammar_rules, extras, externals, name, in_progress)
});
in_progress.remove(target_name);
result
}
pub(crate) fn parse_grammar(input: &str) -> ParseGrammarResult<InputGrammar> {
let mut grammar_json = serde_json::from_str::<GrammarJSON>(input)?;
let mut extra_symbols =
grammar_json
.extras
.into_iter()
.try_fold(Vec::<Rule>::new(), |mut acc, item| {
let rule = parse_rule(item, false)?;
if let Rule::String(ref value) = rule {
if value.is_empty() {
Err(ParseGrammarError::InvalidExtra)?;
}
}
acc.push(rule);
ParseGrammarResult::Ok(acc)
})?;
let mut external_tokens = grammar_json
.externals
.into_iter()
.map(|e| parse_rule(e, false))
.collect::<ParseGrammarResult<Vec<_>>>()?;
let mut precedence_orderings = Vec::with_capacity(grammar_json.precedences.len());
for list in grammar_json.precedences {
let mut ordering = Vec::with_capacity(list.len());
for entry in list {
ordering.push(match entry {
RuleJSON::STRING { value } => PrecedenceEntry::Name(value),
RuleJSON::SYMBOL { name } => PrecedenceEntry::Symbol(name),
_ => Err(ParseGrammarError::Unexpected)?,
});
}
precedence_orderings.push(ordering);
}
let mut variables = Vec::with_capacity(grammar_json.rules.len());
let rules = grammar_json
.rules
.into_iter()
.map(|(n, r)| Ok((n, parse_rule(serde_json::from_value(r)?, false)?)))
.collect::<ParseGrammarResult<Vec<_>>>()?;
let mut in_progress = HashSet::new();
for (name, rule) in &rules {
if !variable_is_used(
&rules,
&extra_symbols,
&external_tokens,
name,
&mut in_progress,
) && grammar_json.word.as_ref().is_none_or(|w| w != name)
{
grammar_json.conflicts.retain(|r| !r.contains(name));
grammar_json.supertypes.retain(|r| r != name);
grammar_json.inline.retain(|r| r != name);
extra_symbols.retain(|r| !rule_is_referenced(r, name, true));
external_tokens.retain(|r| !rule_is_referenced(r, name, true));
precedence_orderings.retain(|r| {
!r.iter().any(|e| {
let PrecedenceEntry::Symbol(s) = e else {
return false;
};
s == name
})
});
continue;
}
variables.push(Variable {
name: name.clone(),
kind: VariableType::Named,
rule: rule.clone(),
});
}
let reserved_words = grammar_json
.reserved
.into_iter()
.map(|(name, rule_values)| {
let mut reserved_words = Vec::new();
let Value::Array(rule_values) = rule_values else {
Err(ParseGrammarError::InvalidReservedWordSet)?
};
for value in rule_values {
reserved_words.push(parse_rule(serde_json::from_value(value)?, false)?);
}
Ok(ReservedWordContext {
name,
reserved_words,
})
})
.collect::<ParseGrammarResult<Vec<_>>>()?;
Ok(InputGrammar {
name: grammar_json.name,
word_token: grammar_json.word,
expected_conflicts: grammar_json.conflicts,
supertype_symbols: grammar_json.supertypes,
variables_to_inline: grammar_json.inline,
precedence_orderings,
variables,
extra_symbols,
external_tokens,
reserved_words,
})
}
fn parse_rule(json: RuleJSON, is_token: bool) -> ParseGrammarResult<Rule> {
match json {
RuleJSON::ALIAS {
content,
value,
named,
} => parse_rule(*content, is_token).map(|r| Rule::alias(r, value, named)),
RuleJSON::BLANK => Ok(Rule::Blank),
RuleJSON::STRING { value } => Ok(Rule::String(value)),
RuleJSON::PATTERN { value, flags } => Ok(Rule::Pattern(
value,
flags.map_or(String::new(), |f| {
f.matches(|c| {
if c == 'i' {
true
} else {
// silently ignore unicode flags
if c != 'u' && c != 'v' {
eprintln!("Warning: unsupported flag {c}");
}
false
}
})
.collect()
}),
)),
RuleJSON::SYMBOL { name } => {
if is_token {
Err(ParseGrammarError::UnexpectedRule(name))?
} else {
Ok(Rule::NamedSymbol(name))
}
}
RuleJSON::CHOICE { members } => members
.into_iter()
.map(|m| parse_rule(m, is_token))
.collect::<ParseGrammarResult<Vec<_>>>()
.map(Rule::choice),
RuleJSON::FIELD { content, name } => {
parse_rule(*content, is_token).map(|r| Rule::field(name, r))
}
RuleJSON::SEQ { members } => members
.into_iter()
.map(|m| parse_rule(m, is_token))
.collect::<ParseGrammarResult<Vec<_>>>()
.map(Rule::seq),
RuleJSON::REPEAT1 { content } => parse_rule(*content, is_token).map(Rule::repeat),
RuleJSON::REPEAT { content } => {
parse_rule(*content, is_token).map(|m| Rule::choice(vec![Rule::repeat(m), Rule::Blank]))
}
RuleJSON::PREC { value, content } => {
parse_rule(*content, is_token).map(|r| Rule::prec(value.into(), r))
}
RuleJSON::PREC_LEFT { value, content } => {
parse_rule(*content, is_token).map(|r| Rule::prec_left(value.into(), r))
}
RuleJSON::PREC_RIGHT { value, content } => {
parse_rule(*content, is_token).map(|r| Rule::prec_right(value.into(), r))
}
RuleJSON::PREC_DYNAMIC { value, content } => {
parse_rule(*content, is_token).map(|r| Rule::prec_dynamic(value, r))
}
RuleJSON::RESERVED {
content,
context_name,
} => parse_rule(*content, is_token).map(|r| Rule::Reserved {
rule: Box::new(r),
context_name,
}),
RuleJSON::TOKEN { content } => parse_rule(*content, true).map(Rule::token),
RuleJSON::IMMEDIATE_TOKEN { content } => {
parse_rule(*content, is_token).map(Rule::immediate_token)
}
}
}
impl From<PrecedenceValueJSON> for Precedence {
fn from(val: PrecedenceValueJSON) -> Self {
match val {
PrecedenceValueJSON::Integer(i) => Self::Integer(i),
PrecedenceValueJSON::Name(i) => Self::Name(i),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_grammar() {
let grammar = parse_grammar(
r#"{
"name": "my_lang",
"rules": {
"file": {
"type": "REPEAT1",
"content": {
"type": "SYMBOL",
"name": "statement"
}
},
"statement": {
"type": "STRING",
"value": "foo"
}
}
}"#,
)
.unwrap();
assert_eq!(grammar.name, "my_lang");
assert_eq!(
grammar.variables,
vec![
Variable {
name: "file".to_string(),
kind: VariableType::Named,
rule: Rule::repeat(Rule::NamedSymbol("statement".to_string()))
},
Variable {
name: "statement".to_string(),
kind: VariableType::Named,
rule: Rule::String("foo".to_string())
},
]
);
}
}

View file

@ -1,289 +0,0 @@
use std::{collections::HashMap, mem};
use super::ExtractedSyntaxGrammar;
use crate::{
grammars::{Variable, VariableType},
rules::{Rule, Symbol},
};
struct Expander {
variable_name: String,
repeat_count_in_variable: usize,
preceding_symbol_count: usize,
auxiliary_variables: Vec<Variable>,
existing_repeats: HashMap<Rule, Symbol>,
}
impl Expander {
fn expand_variable(&mut self, index: usize, variable: &mut Variable) -> bool {
self.variable_name.clear();
self.variable_name.push_str(&variable.name);
self.repeat_count_in_variable = 0;
let mut rule = Rule::Blank;
mem::swap(&mut rule, &mut variable.rule);
// In the special case of a hidden variable with a repetition at its top level,
// convert that rule itself into a binary tree structure instead of introducing
// another auxiliary rule.
if let (VariableType::Hidden, Rule::Repeat(repeated_content)) = (variable.kind, &rule) {
let inner_rule = self.expand_rule(repeated_content);
variable.rule = self.wrap_rule_in_binary_tree(Symbol::non_terminal(index), inner_rule);
variable.kind = VariableType::Auxiliary;
return true;
}
variable.rule = self.expand_rule(&rule);
false
}
fn expand_rule(&mut self, rule: &Rule) -> Rule {
match rule {
// For choices, sequences, and metadata, descend into the child rules,
// replacing any nested repetitions.
Rule::Choice(elements) => Rule::Choice(
elements
.iter()
.map(|element| self.expand_rule(element))
.collect(),
),
Rule::Seq(elements) => Rule::Seq(
elements
.iter()
.map(|element| self.expand_rule(element))
.collect(),
),
Rule::Metadata { rule, params } => Rule::Metadata {
rule: Box::new(self.expand_rule(rule)),
params: params.clone(),
},
// For repetitions, introduce an auxiliary rule that contains the
// repeated content, but can also contain a recursive binary tree structure.
Rule::Repeat(content) => {
let inner_rule = self.expand_rule(content);
if let Some(existing_symbol) = self.existing_repeats.get(&inner_rule) {
return Rule::Symbol(*existing_symbol);
}
self.repeat_count_in_variable += 1;
let rule_name = format!(
"{}_repeat{}",
self.variable_name, self.repeat_count_in_variable
);
let repeat_symbol = Symbol::non_terminal(
self.preceding_symbol_count + self.auxiliary_variables.len(),
);
self.existing_repeats
.insert(inner_rule.clone(), repeat_symbol);
self.auxiliary_variables.push(Variable {
name: rule_name,
kind: VariableType::Auxiliary,
rule: self.wrap_rule_in_binary_tree(repeat_symbol, inner_rule),
});
Rule::Symbol(repeat_symbol)
}
// For primitive rules, don't change anything.
_ => rule.clone(),
}
}
fn wrap_rule_in_binary_tree(&self, symbol: Symbol, rule: Rule) -> Rule {
Rule::choice(vec![
Rule::Seq(vec![Rule::Symbol(symbol), Rule::Symbol(symbol)]),
rule,
])
}
}
pub(super) fn expand_repeats(mut grammar: ExtractedSyntaxGrammar) -> ExtractedSyntaxGrammar {
let mut expander = Expander {
variable_name: String::new(),
repeat_count_in_variable: 0,
preceding_symbol_count: grammar.variables.len(),
auxiliary_variables: Vec::new(),
existing_repeats: HashMap::new(),
};
for (i, variable) in grammar.variables.iter_mut().enumerate() {
let expanded_top_level_repetition = expander.expand_variable(i, variable);
// If a hidden variable had a top-level repetition and it was converted to
// a recursive rule, then it can't be inlined.
if expanded_top_level_repetition {
grammar
.variables_to_inline
.retain(|symbol| *symbol != Symbol::non_terminal(i));
}
}
grammar.variables.extend(expander.auxiliary_variables);
grammar
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_repeat_expansion() {
// Repeats nested inside of sequences and choices are expanded.
let grammar = expand_repeats(build_grammar(vec![Variable::named(
"rule0",
Rule::seq(vec![
Rule::terminal(10),
Rule::choice(vec![
Rule::repeat(Rule::terminal(11)),
Rule::repeat(Rule::terminal(12)),
]),
Rule::terminal(13),
]),
)]));
assert_eq!(
grammar.variables,
vec![
Variable::named(
"rule0",
Rule::seq(vec![
Rule::terminal(10),
Rule::choice(vec![Rule::non_terminal(1), Rule::non_terminal(2),]),
Rule::terminal(13),
])
),
Variable::auxiliary(
"rule0_repeat1",
Rule::choice(vec![
Rule::seq(vec![Rule::non_terminal(1), Rule::non_terminal(1),]),
Rule::terminal(11),
])
),
Variable::auxiliary(
"rule0_repeat2",
Rule::choice(vec![
Rule::seq(vec![Rule::non_terminal(2), Rule::non_terminal(2),]),
Rule::terminal(12),
])
),
]
);
}
#[test]
fn test_repeat_deduplication() {
// Terminal 4 appears inside of a repeat in three different places.
let grammar = expand_repeats(build_grammar(vec![
Variable::named(
"rule0",
Rule::choice(vec![
Rule::seq(vec![Rule::terminal(1), Rule::repeat(Rule::terminal(4))]),
Rule::seq(vec![Rule::terminal(2), Rule::repeat(Rule::terminal(4))]),
]),
),
Variable::named(
"rule1",
Rule::seq(vec![Rule::terminal(3), Rule::repeat(Rule::terminal(4))]),
),
]));
// Only one auxiliary rule is created for repeating terminal 4.
assert_eq!(
grammar.variables,
vec![
Variable::named(
"rule0",
Rule::choice(vec![
Rule::seq(vec![Rule::terminal(1), Rule::non_terminal(2)]),
Rule::seq(vec![Rule::terminal(2), Rule::non_terminal(2)]),
])
),
Variable::named(
"rule1",
Rule::seq(vec![Rule::terminal(3), Rule::non_terminal(2),])
),
Variable::auxiliary(
"rule0_repeat1",
Rule::choice(vec![
Rule::seq(vec![Rule::non_terminal(2), Rule::non_terminal(2),]),
Rule::terminal(4),
])
)
]
);
}
#[test]
fn test_expansion_of_nested_repeats() {
let grammar = expand_repeats(build_grammar(vec![Variable::named(
"rule0",
Rule::seq(vec![
Rule::terminal(10),
Rule::repeat(Rule::seq(vec![
Rule::terminal(11),
Rule::repeat(Rule::terminal(12)),
])),
]),
)]));
assert_eq!(
grammar.variables,
vec![
Variable::named(
"rule0",
Rule::seq(vec![Rule::terminal(10), Rule::non_terminal(2),])
),
Variable::auxiliary(
"rule0_repeat1",
Rule::choice(vec![
Rule::seq(vec![Rule::non_terminal(1), Rule::non_terminal(1),]),
Rule::terminal(12),
])
),
Variable::auxiliary(
"rule0_repeat2",
Rule::choice(vec![
Rule::seq(vec![Rule::non_terminal(2), Rule::non_terminal(2),]),
Rule::seq(vec![Rule::terminal(11), Rule::non_terminal(1),]),
])
),
]
);
}
#[test]
fn test_expansion_of_repeats_at_top_of_hidden_rules() {
let grammar = expand_repeats(build_grammar(vec![
Variable::named("rule0", Rule::non_terminal(1)),
Variable::hidden(
"_rule1",
Rule::repeat(Rule::choice(vec![Rule::terminal(11), Rule::terminal(12)])),
),
]));
assert_eq!(
grammar.variables,
vec![
Variable::named("rule0", Rule::non_terminal(1),),
Variable::auxiliary(
"_rule1",
Rule::choice(vec![
Rule::seq(vec![Rule::non_terminal(1), Rule::non_terminal(1)]),
Rule::terminal(11),
Rule::terminal(12),
]),
),
]
);
}
fn build_grammar(variables: Vec<Variable>) -> ExtractedSyntaxGrammar {
ExtractedSyntaxGrammar {
variables,
..Default::default()
}
}
}

View file

@ -1,826 +0,0 @@
use anyhow::Result;
use regex_syntax::{
hir::{Class, Hir, HirKind},
ParserBuilder,
};
use serde::Serialize;
use thiserror::Error;
use super::ExtractedLexicalGrammar;
use crate::{
grammars::{LexicalGrammar, LexicalVariable},
nfa::{CharacterSet, Nfa, NfaState},
rules::{Precedence, Rule},
};
struct NfaBuilder {
nfa: Nfa,
is_sep: bool,
precedence_stack: Vec<i32>,
}
pub type ExpandTokensResult<T> = Result<T, ExpandTokensError>;
#[derive(Debug, Error, Serialize)]
pub enum ExpandTokensError {
#[error(
"The rule `{0}` matches the empty string.
Tree-sitter does not support syntactic rules that match the empty string
unless they are used only as the grammar's start rule.
"
)]
EmptyString(String),
#[error(transparent)]
Processing(ExpandTokensProcessingError),
#[error(transparent)]
ExpandRule(ExpandRuleError),
}
#[derive(Debug, Error, Serialize)]
pub struct ExpandTokensProcessingError {
rule: String,
error: ExpandRuleError,
}
impl std::fmt::Display for ExpandTokensProcessingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"Error processing rule {}: Grammar error: Unexpected rule {:?}",
self.rule, self.error
)?;
Ok(())
}
}
fn get_implicit_precedence(rule: &Rule) -> i32 {
match rule {
Rule::String(_) => 2,
Rule::Metadata { rule, params } => {
if params.is_main_token {
get_implicit_precedence(rule) + 1
} else {
get_implicit_precedence(rule)
}
}
_ => 0,
}
}
const fn get_completion_precedence(rule: &Rule) -> i32 {
if let Rule::Metadata { params, .. } = rule {
if let Precedence::Integer(p) = params.precedence {
return p;
}
}
0
}
pub fn expand_tokens(mut grammar: ExtractedLexicalGrammar) -> ExpandTokensResult<LexicalGrammar> {
let mut builder = NfaBuilder {
nfa: Nfa::new(),
is_sep: true,
precedence_stack: vec![0],
};
let separator_rule = if grammar.separators.is_empty() {
Rule::Blank
} else {
grammar.separators.push(Rule::Blank);
Rule::repeat(Rule::choice(grammar.separators))
};
let mut variables = Vec::new();
for (i, variable) in grammar.variables.into_iter().enumerate() {
if variable.rule.is_empty() {
Err(ExpandTokensError::EmptyString(variable.name.clone()))?;
}
let is_immediate_token = match &variable.rule {
Rule::Metadata { params, .. } => params.is_main_token,
_ => false,
};
builder.is_sep = false;
builder.nfa.states.push(NfaState::Accept {
variable_index: i,
precedence: get_completion_precedence(&variable.rule),
});
let last_state_id = builder.nfa.last_state_id();
builder
.expand_rule(&variable.rule, last_state_id)
.map_err(|e| {
ExpandTokensError::Processing(ExpandTokensProcessingError {
rule: variable.name.clone(),
error: e,
})
})?;
if !is_immediate_token {
builder.is_sep = true;
let last_state_id = builder.nfa.last_state_id();
builder
.expand_rule(&separator_rule, last_state_id)
.map_err(ExpandTokensError::ExpandRule)?;
}
variables.push(LexicalVariable {
name: variable.name,
kind: variable.kind,
implicit_precedence: get_implicit_precedence(&variable.rule),
start_state: builder.nfa.last_state_id(),
});
}
Ok(LexicalGrammar {
nfa: builder.nfa,
variables,
})
}
pub type ExpandRuleResult<T> = Result<T, ExpandRuleError>;
#[derive(Debug, Error, Serialize)]
pub enum ExpandRuleError {
#[error("Grammar error: Unexpected rule {0:?}")]
UnexpectedRule(Rule),
#[error("{0}")]
Parse(String),
#[error(transparent)]
ExpandRegex(ExpandRegexError),
}
pub type ExpandRegexResult<T> = Result<T, ExpandRegexError>;
#[derive(Debug, Error, Serialize)]
pub enum ExpandRegexError {
#[error("{0}")]
Utf8(String),
#[error("Regex error: Assertions are not supported")]
Assertion,
}
impl NfaBuilder {
fn expand_rule(&mut self, rule: &Rule, mut next_state_id: u32) -> ExpandRuleResult<bool> {
match rule {
Rule::Pattern(s, f) => {
// With unicode enabled, `\w`, `\s` and `\d` expand to character sets that are much
// larger than intended, so we replace them with the actual
// character sets they should represent. If the full unicode range
// of `\w`, `\s` or `\d` are needed then `\p{L}`, `\p{Z}` and `\p{N}` should be
// used.
let s = s
.replace(r"\w", r"[0-9A-Za-z_]")
.replace(r"\s", r"[\t-\r ]")
.replace(r"\d", r"[0-9]")
.replace(r"\W", r"[^0-9A-Za-z_]")
.replace(r"\S", r"[^\t-\r ]")
.replace(r"\D", r"[^0-9]");
let mut parser = ParserBuilder::new()
.case_insensitive(f.contains('i'))
.unicode(true)
.utf8(false)
.build();
let hir = parser
.parse(&s)
.map_err(|e| ExpandRuleError::Parse(e.to_string()))?;
self.expand_regex(&hir, next_state_id)
.map_err(ExpandRuleError::ExpandRegex)
}
Rule::String(s) => {
for c in s.chars().rev() {
self.push_advance(CharacterSet::empty().add_char(c), next_state_id);
next_state_id = self.nfa.last_state_id();
}
Ok(!s.is_empty())
}
Rule::Choice(elements) => {
let mut alternative_state_ids = Vec::new();
for element in elements {
if self.expand_rule(element, next_state_id)? {
alternative_state_ids.push(self.nfa.last_state_id());
} else {
alternative_state_ids.push(next_state_id);
}
}
alternative_state_ids.sort_unstable();
alternative_state_ids.dedup();
alternative_state_ids.retain(|i| *i != self.nfa.last_state_id());
for alternative_state_id in alternative_state_ids {
self.push_split(alternative_state_id);
}
Ok(true)
}
Rule::Seq(elements) => {
let mut result = false;
for element in elements.iter().rev() {
if self.expand_rule(element, next_state_id)? {
result = true;
}
next_state_id = self.nfa.last_state_id();
}
Ok(result)
}
Rule::Repeat(rule) => {
self.nfa.states.push(NfaState::Accept {
variable_index: 0,
precedence: 0,
}); // Placeholder for split
let split_state_id = self.nfa.last_state_id();
if self.expand_rule(rule, split_state_id)? {
self.nfa.states[split_state_id as usize] =
NfaState::Split(self.nfa.last_state_id(), next_state_id);
Ok(true)
} else {
Ok(false)
}
}
Rule::Metadata { rule, params } => {
let has_precedence = if let Precedence::Integer(precedence) = &params.precedence {
self.precedence_stack.push(*precedence);
true
} else {
false
};
let result = self.expand_rule(rule, next_state_id);
if has_precedence {
self.precedence_stack.pop();
}
result
}
Rule::Blank => Ok(false),
_ => Err(ExpandRuleError::UnexpectedRule(rule.clone()))?,
}
}
fn expand_regex(&mut self, hir: &Hir, mut next_state_id: u32) -> ExpandRegexResult<bool> {
match hir.kind() {
HirKind::Empty => Ok(false),
HirKind::Literal(literal) => {
for character in std::str::from_utf8(&literal.0)
.map_err(|e| ExpandRegexError::Utf8(e.to_string()))?
.chars()
.rev()
{
let char_set = CharacterSet::from_char(character);
self.push_advance(char_set, next_state_id);
next_state_id = self.nfa.last_state_id();
}
Ok(true)
}
HirKind::Class(class) => match class {
Class::Unicode(class) => {
let mut chars = CharacterSet::default();
for c in class.ranges() {
chars = chars.add_range(c.start(), c.end());
}
// For some reason, the long s `ſ` is included if the letter `s` is in a
// pattern, so we remove it.
if chars.range_count() == 3
&& chars
.ranges()
// exact check to ensure that `ſ` wasn't intentionally added.
.all(|r| ['s'..='s', 'S'..='S', 'ſ'..='ſ'].contains(&r))
{
chars = chars.difference(CharacterSet::from_char('ſ'));
}
self.push_advance(chars, next_state_id);
Ok(true)
}
Class::Bytes(bytes_class) => {
let mut chars = CharacterSet::default();
for c in bytes_class.ranges() {
chars = chars.add_range(c.start().into(), c.end().into());
}
self.push_advance(chars, next_state_id);
Ok(true)
}
},
HirKind::Look(_) => Err(ExpandRegexError::Assertion)?,
HirKind::Repetition(repetition) => match (repetition.min, repetition.max) {
(0, Some(1)) => self.expand_zero_or_one(&repetition.sub, next_state_id),
(1, None) => self.expand_one_or_more(&repetition.sub, next_state_id),
(0, None) => self.expand_zero_or_more(&repetition.sub, next_state_id),
(min, Some(max)) if min == max => {
self.expand_count(&repetition.sub, min, next_state_id)
}
(min, None) => {
if self.expand_zero_or_more(&repetition.sub, next_state_id)? {
self.expand_count(&repetition.sub, min, next_state_id)
} else {
Ok(false)
}
}
(min, Some(max)) => {
let mut result = self.expand_count(&repetition.sub, min, next_state_id)?;
for _ in min..max {
if result {
next_state_id = self.nfa.last_state_id();
}
if self.expand_zero_or_one(&repetition.sub, next_state_id)? {
result = true;
}
}
Ok(result)
}
},
HirKind::Capture(capture) => self.expand_regex(&capture.sub, next_state_id),
HirKind::Concat(concat) => {
let mut result = false;
for hir in concat.iter().rev() {
if self.expand_regex(hir, next_state_id)? {
result = true;
next_state_id = self.nfa.last_state_id();
}
}
Ok(result)
}
HirKind::Alternation(alternations) => {
let mut alternative_state_ids = Vec::new();
for hir in alternations {
if self.expand_regex(hir, next_state_id)? {
alternative_state_ids.push(self.nfa.last_state_id());
} else {
alternative_state_ids.push(next_state_id);
}
}
alternative_state_ids.sort_unstable();
alternative_state_ids.dedup();
alternative_state_ids.retain(|i| *i != self.nfa.last_state_id());
for alternative_state_id in alternative_state_ids {
self.push_split(alternative_state_id);
}
Ok(true)
}
}
}
fn expand_one_or_more(&mut self, hir: &Hir, next_state_id: u32) -> ExpandRegexResult<bool> {
self.nfa.states.push(NfaState::Accept {
variable_index: 0,
precedence: 0,
}); // Placeholder for split
let split_state_id = self.nfa.last_state_id();
if self.expand_regex(hir, split_state_id)? {
self.nfa.states[split_state_id as usize] =
NfaState::Split(self.nfa.last_state_id(), next_state_id);
Ok(true)
} else {
self.nfa.states.pop();
Ok(false)
}
}
fn expand_zero_or_one(&mut self, hir: &Hir, next_state_id: u32) -> ExpandRegexResult<bool> {
if self.expand_regex(hir, next_state_id)? {
self.push_split(next_state_id);
Ok(true)
} else {
Ok(false)
}
}
fn expand_zero_or_more(&mut self, hir: &Hir, next_state_id: u32) -> ExpandRegexResult<bool> {
if self.expand_one_or_more(hir, next_state_id)? {
self.push_split(next_state_id);
Ok(true)
} else {
Ok(false)
}
}
fn expand_count(
&mut self,
hir: &Hir,
count: u32,
mut next_state_id: u32,
) -> ExpandRegexResult<bool> {
let mut result = false;
for _ in 0..count {
if self.expand_regex(hir, next_state_id)? {
result = true;
next_state_id = self.nfa.last_state_id();
}
}
Ok(result)
}
fn push_advance(&mut self, chars: CharacterSet, state_id: u32) {
let precedence = *self.precedence_stack.last().unwrap();
self.nfa.states.push(NfaState::Advance {
chars,
state_id,
precedence,
is_sep: self.is_sep,
});
}
fn push_split(&mut self, state_id: u32) {
let last_state_id = self.nfa.last_state_id();
self.nfa
.states
.push(NfaState::Split(state_id, last_state_id));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
grammars::Variable,
nfa::{NfaCursor, NfaTransition},
};
fn simulate_nfa<'a>(grammar: &'a LexicalGrammar, s: &'a str) -> Option<(usize, &'a str)> {
let start_states = grammar.variables.iter().map(|v| v.start_state).collect();
let mut cursor = NfaCursor::new(&grammar.nfa, start_states);
let mut result = None;
let mut result_precedence = i32::MIN;
let mut start_char = 0;
let mut end_char = 0;
for c in s.chars() {
for (id, precedence) in cursor.completions() {
if result.is_none() || result_precedence <= precedence {
result = Some((id, &s[start_char..end_char]));
result_precedence = precedence;
}
}
if let Some(NfaTransition {
states,
is_separator,
..
}) = cursor
.transitions()
.into_iter()
.find(|t| t.characters.contains(c) && t.precedence >= result_precedence)
{
cursor.reset(states);
end_char += c.len_utf8();
if is_separator {
start_char = end_char;
}
} else {
break;
}
}
for (id, precedence) in cursor.completions() {
if result.is_none() || result_precedence <= precedence {
result = Some((id, &s[start_char..end_char]));
result_precedence = precedence;
}
}
result
}
#[test]
fn test_rule_expansion() {
struct Row {
rules: Vec<Rule>,
separators: Vec<Rule>,
examples: Vec<(&'static str, Option<(usize, &'static str)>)>,
}
let table = [
// regex with sequences and alternatives
Row {
rules: vec![Rule::pattern("(a|b|c)d(e|f|g)h?", "")],
separators: vec![],
examples: vec![
("ade1", Some((0, "ade"))),
("bdf1", Some((0, "bdf"))),
("bdfh1", Some((0, "bdfh"))),
("ad1", None),
],
},
// regex with repeats
Row {
rules: vec![Rule::pattern("a*", "")],
separators: vec![],
examples: vec![("aaa1", Some((0, "aaa"))), ("b", Some((0, "")))],
},
// regex with repeats in sequences
Row {
rules: vec![Rule::pattern("a((bc)+|(de)*)f", "")],
separators: vec![],
examples: vec![
("af1", Some((0, "af"))),
("adedef1", Some((0, "adedef"))),
("abcbcbcf1", Some((0, "abcbcbcf"))),
("a", None),
],
},
// regex with character ranges
Row {
rules: vec![Rule::pattern("[a-fA-F0-9]+", "")],
separators: vec![],
examples: vec![("A1ff0.", Some((0, "A1ff0")))],
},
// regex with perl character classes
Row {
rules: vec![Rule::pattern("\\w\\d\\s", "")],
separators: vec![],
examples: vec![("_0 ", Some((0, "_0 ")))],
},
// string
Row {
rules: vec![Rule::string("abc")],
separators: vec![],
examples: vec![("abcd", Some((0, "abc"))), ("ab", None)],
},
// complex rule containing strings and regexes
Row {
rules: vec![Rule::repeat(Rule::seq(vec![
Rule::string("{"),
Rule::pattern("[a-f]+", ""),
Rule::string("}"),
]))],
separators: vec![],
examples: vec![
("{a}{", Some((0, "{a}"))),
("{a}{d", Some((0, "{a}"))),
("ab", None),
],
},
// longest match rule
Row {
rules: vec![
Rule::pattern("a|bc", ""),
Rule::pattern("aa", ""),
Rule::pattern("bcd", ""),
],
separators: vec![],
examples: vec![
("a.", Some((0, "a"))),
("bc.", Some((0, "bc"))),
("aa.", Some((1, "aa"))),
("bcd?", Some((2, "bcd"))),
("b.", None),
("c.", None),
],
},
// regex with an alternative including the empty string
Row {
rules: vec![Rule::pattern("a(b|)+c", "")],
separators: vec![],
examples: vec![
("ac.", Some((0, "ac"))),
("abc.", Some((0, "abc"))),
("abbc.", Some((0, "abbc"))),
],
},
// separators
Row {
rules: vec![Rule::pattern("[a-f]+", "")],
separators: vec![Rule::string("\\\n"), Rule::pattern("\\s", "")],
examples: vec![
(" a", Some((0, "a"))),
(" \nb", Some((0, "b"))),
(" \\a", None),
(" \\\na", Some((0, "a"))),
],
},
// shorter tokens with higher precedence
Row {
rules: vec![
Rule::prec(Precedence::Integer(2), Rule::pattern("abc", "")),
Rule::prec(Precedence::Integer(1), Rule::pattern("ab[cd]e", "")),
Rule::pattern("[a-e]+", ""),
],
separators: vec![Rule::string("\\\n"), Rule::pattern("\\s", "")],
examples: vec![
("abceef", Some((0, "abc"))),
("abdeef", Some((1, "abde"))),
("aeeeef", Some((2, "aeeee"))),
],
},
// immediate tokens with higher precedence
Row {
rules: vec![
Rule::prec(Precedence::Integer(1), Rule::pattern("[^a]+", "")),
Rule::immediate_token(Rule::prec(
Precedence::Integer(2),
Rule::pattern("[^ab]+", ""),
)),
],
separators: vec![Rule::pattern("\\s", "")],
examples: vec![("cccb", Some((1, "ccc")))],
},
Row {
rules: vec![Rule::seq(vec![
Rule::string("a"),
Rule::choice(vec![Rule::string("b"), Rule::string("c")]),
Rule::string("d"),
])],
separators: vec![],
examples: vec![
("abd", Some((0, "abd"))),
("acd", Some((0, "acd"))),
("abc", None),
("ad", None),
("d", None),
("a", None),
],
},
// nested choices within sequences
Row {
rules: vec![Rule::seq(vec![
Rule::pattern("[0-9]+", ""),
Rule::choice(vec![
Rule::Blank,
Rule::choice(vec![Rule::seq(vec![
Rule::choice(vec![Rule::string("e"), Rule::string("E")]),
Rule::choice(vec![
Rule::Blank,
Rule::choice(vec![Rule::string("+"), Rule::string("-")]),
]),
Rule::pattern("[0-9]+", ""),
])]),
]),
])],
separators: vec![],
examples: vec![
("12", Some((0, "12"))),
("12e", Some((0, "12"))),
("12g", Some((0, "12"))),
("12e3", Some((0, "12e3"))),
("12e+", Some((0, "12"))),
("12E+34 +", Some((0, "12E+34"))),
("12e34", Some((0, "12e34"))),
],
},
// nested groups
Row {
rules: vec![Rule::seq(vec![Rule::pattern(r"([^x\\]|\\(.|\n))+", "")])],
separators: vec![],
examples: vec![("abcx", Some((0, "abc"))), ("abc\\0x", Some((0, "abc\\0")))],
},
// allowing unrecognized escape sequences
Row {
rules: vec![
// Escaped forward slash (used in JS because '/' is the regex delimiter)
Rule::pattern(r"\/", ""),
// Escaped quotes
Rule::pattern(r#"\"\'"#, ""),
// Quote preceded by a literal backslash
Rule::pattern(r"[\\']+", ""),
],
separators: vec![],
examples: vec![
("/", Some((0, "/"))),
("\"\'", Some((1, "\"\'"))),
(r"'\'a", Some((2, r"'\'"))),
],
},
// unicode property escapes
Row {
rules: vec![
Rule::pattern(r"\p{L}+\P{L}+", ""),
Rule::pattern(r"\p{White_Space}+\P{White_Space}+[\p{White_Space}]*", ""),
],
separators: vec![],
examples: vec![
(" 123 abc", Some((1, " 123 "))),
("ბΨƁ___ƀƔ", Some((0, "ბΨƁ___"))),
],
},
// unicode property escapes in bracketed sets
Row {
rules: vec![Rule::pattern(r"[\p{L}\p{Nd}]+", "")],
separators: vec![],
examples: vec![("abΨ12٣٣, ok", Some((0, "abΨ12٣٣")))],
},
// unicode character escapes
Row {
rules: vec![
Rule::pattern(r"\u{00dc}", ""),
Rule::pattern(r"\U{000000dd}", ""),
Rule::pattern(r"\u00de", ""),
Rule::pattern(r"\U000000df", ""),
],
separators: vec![],
examples: vec![
("\u{00dc}", Some((0, "\u{00dc}"))),
("\u{00dd}", Some((1, "\u{00dd}"))),
("\u{00de}", Some((2, "\u{00de}"))),
("\u{00df}", Some((3, "\u{00df}"))),
],
},
Row {
rules: vec![
Rule::pattern(r"u\{[0-9a-fA-F]+\}", ""),
// Already-escaped curly braces
Rule::pattern(r"\{[ab]{3}\}", ""),
// Unicode codepoints
Rule::pattern(r"\u{1000A}", ""),
// Unicode codepoints (lowercase)
Rule::pattern(r"\u{1000b}", ""),
],
separators: vec![],
examples: vec![
("u{1234} ok", Some((0, "u{1234}"))),
("{aba}}", Some((1, "{aba}"))),
("\u{1000A}", Some((2, "\u{1000A}"))),
("\u{1000b}", Some((3, "\u{1000b}"))),
],
},
// Emojis
Row {
rules: vec![Rule::pattern(r"\p{Emoji}+", "")],
separators: vec![],
examples: vec![
("🐎", Some((0, "🐎"))),
("🐴🐴", Some((0, "🐴🐴"))),
("#0", Some((0, "#0"))), // These chars are technically emojis!
("", None),
("", None),
("horse", None),
],
},
// Intersection
Row {
rules: vec![Rule::pattern(r"[[0-7]&&[4-9]]+", "")],
separators: vec![],
examples: vec![
("456", Some((0, "456"))),
("64", Some((0, "64"))),
("452", Some((0, "45"))),
("91", None),
("8", None),
("3", None),
],
},
// Difference
Row {
rules: vec![Rule::pattern(r"[[0-9]--[4-7]]+", "")],
separators: vec![],
examples: vec![
("123", Some((0, "123"))),
("83", Some((0, "83"))),
("9", Some((0, "9"))),
("124", Some((0, "12"))),
("67", None),
("4", None),
],
},
// Symmetric difference
Row {
rules: vec![Rule::pattern(r"[[0-7]~~[4-9]]+", "")],
separators: vec![],
examples: vec![
("123", Some((0, "123"))),
("83", Some((0, "83"))),
("9", Some((0, "9"))),
("124", Some((0, "12"))),
("67", None),
("4", None),
],
},
// Nested set operations
Row {
// 0 1 2 3 4 5 6 7 8 9
// [0-5]: y y y y y y
// [2-4]: y y y
// [0-5]--[2-4]: y y y
// [3-9]: y y y y y y y
// [6-7]: y y
// [3-9]--[5-7]: y y y y y
// final regex: y y y y y y
rules: vec![Rule::pattern(r"[[[0-5]--[2-4]]~~[[3-9]--[6-7]]]+", "")],
separators: vec![],
examples: vec![
("01", Some((0, "01"))),
("432", Some((0, "43"))),
("8", Some((0, "8"))),
("9", Some((0, "9"))),
("2", None),
("567", None),
],
},
];
for Row {
rules,
separators,
examples,
} in &table
{
let grammar = expand_tokens(ExtractedLexicalGrammar {
separators: separators.clone(),
variables: rules
.iter()
.map(|rule| Variable::named("", rule.clone()))
.collect(),
})
.unwrap();
for (haystack, needle) in examples {
assert_eq!(simulate_nfa(&grammar, haystack), *needle);
}
}
}
}

View file

@ -1,304 +0,0 @@
use crate::{
grammars::{LexicalGrammar, SyntaxGrammar},
rules::{Alias, AliasMap, Symbol, SymbolType},
};
#[derive(Clone, Default)]
struct SymbolStatus {
aliases: Vec<(Alias, usize)>,
appears_unaliased: bool,
}
// Update the grammar by finding symbols that always are aliased, and for each such symbol,
// promoting one of its aliases to a "default alias", which is applied globally instead
// of in a context-specific way.
//
// This has two benefits:
// * It reduces the overhead of storing production-specific alias info in the parse table.
// * Within an `ERROR` node, no context-specific aliases will be applied. This transformation
// ensures that the children of an `ERROR` node have symbols that are consistent with the way that
// they would appear in a valid syntax tree.
pub(super) fn extract_default_aliases(
syntax_grammar: &mut SyntaxGrammar,
lexical_grammar: &LexicalGrammar,
) -> AliasMap {
let mut terminal_status_list = vec![SymbolStatus::default(); lexical_grammar.variables.len()];
let mut non_terminal_status_list =
vec![SymbolStatus::default(); syntax_grammar.variables.len()];
let mut external_status_list =
vec![SymbolStatus::default(); syntax_grammar.external_tokens.len()];
// For each grammar symbol, find all of the aliases under which the symbol appears,
// and determine whether or not the symbol ever appears *unaliased*.
for variable in &syntax_grammar.variables {
for production in &variable.productions {
for step in &production.steps {
let status = match step.symbol.kind {
SymbolType::External => &mut external_status_list[step.symbol.index],
SymbolType::NonTerminal => &mut non_terminal_status_list[step.symbol.index],
SymbolType::Terminal => &mut terminal_status_list[step.symbol.index],
SymbolType::End | SymbolType::EndOfNonTerminalExtra => {
panic!("Unexpected end token")
}
};
// Default aliases don't work for inlined variables.
if syntax_grammar.variables_to_inline.contains(&step.symbol) {
continue;
}
if let Some(alias) = &step.alias {
if let Some(count_for_alias) = status
.aliases
.iter_mut()
.find_map(|(a, count)| if a == alias { Some(count) } else { None })
{
*count_for_alias += 1;
} else {
status.aliases.push((alias.clone(), 1));
}
} else {
status.appears_unaliased = true;
}
}
}
}
for symbol in &syntax_grammar.extra_symbols {
let status = match symbol.kind {
SymbolType::External => &mut external_status_list[symbol.index],
SymbolType::NonTerminal => &mut non_terminal_status_list[symbol.index],
SymbolType::Terminal => &mut terminal_status_list[symbol.index],
SymbolType::End | SymbolType::EndOfNonTerminalExtra => {
panic!("Unexpected end token")
}
};
status.appears_unaliased = true;
}
let symbols_with_statuses = (terminal_status_list
.iter_mut()
.enumerate()
.map(|(i, status)| (Symbol::terminal(i), status)))
.chain(
non_terminal_status_list
.iter_mut()
.enumerate()
.map(|(i, status)| (Symbol::non_terminal(i), status)),
)
.chain(
external_status_list
.iter_mut()
.enumerate()
.map(|(i, status)| (Symbol::external(i), status)),
);
// For each symbol that always appears aliased, find the alias the occurs most often,
// and designate that alias as the symbol's "default alias". Store all of these
// default aliases in a map that will be returned.
let mut result = AliasMap::new();
for (symbol, status) in symbols_with_statuses {
if status.appears_unaliased {
status.aliases.clear();
} else if let Some(default_entry) = status
.aliases
.iter()
.enumerate()
.max_by_key(|(i, (_, count))| (count, -(*i as i64)))
.map(|(_, entry)| entry.clone())
{
status.aliases.clear();
status.aliases.push(default_entry.clone());
result.insert(symbol, default_entry.0);
}
}
// Wherever a symbol is aliased as its default alias, remove the usage of the alias,
// because it will now be redundant.
let mut alias_positions_to_clear = Vec::new();
for variable in &mut syntax_grammar.variables {
alias_positions_to_clear.clear();
for (i, production) in variable.productions.iter().enumerate() {
for (j, step) in production.steps.iter().enumerate() {
let status = match step.symbol.kind {
SymbolType::External => &mut external_status_list[step.symbol.index],
SymbolType::NonTerminal => &mut non_terminal_status_list[step.symbol.index],
SymbolType::Terminal => &mut terminal_status_list[step.symbol.index],
SymbolType::End | SymbolType::EndOfNonTerminalExtra => {
panic!("Unexpected end token")
}
};
// If this step is aliased as the symbol's default alias, then remove that alias.
if step.alias.is_some()
&& step.alias.as_ref() == status.aliases.first().map(|t| &t.0)
{
let mut other_productions_must_use_this_alias_at_this_index = false;
for (other_i, other_production) in variable.productions.iter().enumerate() {
if other_i != i
&& other_production.steps.len() > j
&& other_production.steps[j].alias == step.alias
&& result.get(&other_production.steps[j].symbol) != step.alias.as_ref()
{
other_productions_must_use_this_alias_at_this_index = true;
break;
}
}
if !other_productions_must_use_this_alias_at_this_index {
alias_positions_to_clear.push((i, j));
}
}
}
}
for (production_index, step_index) in &alias_positions_to_clear {
variable.productions[*production_index].steps[*step_index].alias = None;
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
grammars::{LexicalVariable, Production, ProductionStep, SyntaxVariable, VariableType},
nfa::Nfa,
};
#[test]
fn test_extract_simple_aliases() {
let mut syntax_grammar = SyntaxGrammar {
variables: vec![
SyntaxVariable {
name: "v1".to_owned(),
kind: VariableType::Named,
productions: vec![Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(0)).with_alias("a1", true),
ProductionStep::new(Symbol::terminal(1)).with_alias("a2", true),
ProductionStep::new(Symbol::terminal(2)).with_alias("a3", true),
ProductionStep::new(Symbol::terminal(3)).with_alias("a4", true),
],
}],
},
SyntaxVariable {
name: "v2".to_owned(),
kind: VariableType::Named,
productions: vec![Production {
dynamic_precedence: 0,
steps: vec![
// Token 0 is always aliased as "a1".
ProductionStep::new(Symbol::terminal(0)).with_alias("a1", true),
// Token 1 is aliased within rule `v1` above, but not here.
ProductionStep::new(Symbol::terminal(1)),
// Token 2 is aliased differently here than in `v1`. The alias from
// `v1` should be promoted to the default alias, because `v1` appears
// first in the grammar.
ProductionStep::new(Symbol::terminal(2)).with_alias("a5", true),
// Token 3 is also aliased differently here than in `v1`. In this case,
// this alias should be promoted to the default alias, because it is
// used a greater number of times (twice).
ProductionStep::new(Symbol::terminal(3)).with_alias("a6", true),
ProductionStep::new(Symbol::terminal(3)).with_alias("a6", true),
],
}],
},
],
..Default::default()
};
let lexical_grammar = LexicalGrammar {
nfa: Nfa::new(),
variables: vec![
LexicalVariable {
name: "t0".to_string(),
kind: VariableType::Anonymous,
implicit_precedence: 0,
start_state: 0,
},
LexicalVariable {
name: "t1".to_string(),
kind: VariableType::Anonymous,
implicit_precedence: 0,
start_state: 0,
},
LexicalVariable {
name: "t2".to_string(),
kind: VariableType::Anonymous,
implicit_precedence: 0,
start_state: 0,
},
LexicalVariable {
name: "t3".to_string(),
kind: VariableType::Anonymous,
implicit_precedence: 0,
start_state: 0,
},
],
};
let default_aliases = extract_default_aliases(&mut syntax_grammar, &lexical_grammar);
assert_eq!(default_aliases.len(), 3);
assert_eq!(
default_aliases.get(&Symbol::terminal(0)),
Some(&Alias {
value: "a1".to_string(),
is_named: true,
})
);
assert_eq!(
default_aliases.get(&Symbol::terminal(2)),
Some(&Alias {
value: "a3".to_string(),
is_named: true,
})
);
assert_eq!(
default_aliases.get(&Symbol::terminal(3)),
Some(&Alias {
value: "a6".to_string(),
is_named: true,
})
);
assert_eq!(default_aliases.get(&Symbol::terminal(1)), None);
assert_eq!(
syntax_grammar.variables,
vec![
SyntaxVariable {
name: "v1".to_owned(),
kind: VariableType::Named,
productions: vec![Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(0)),
ProductionStep::new(Symbol::terminal(1)).with_alias("a2", true),
ProductionStep::new(Symbol::terminal(2)),
ProductionStep::new(Symbol::terminal(3)).with_alias("a4", true),
],
},],
},
SyntaxVariable {
name: "v2".to_owned(),
kind: VariableType::Named,
productions: vec![Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(0)),
ProductionStep::new(Symbol::terminal(1)),
ProductionStep::new(Symbol::terminal(2)).with_alias("a5", true),
ProductionStep::new(Symbol::terminal(3)),
ProductionStep::new(Symbol::terminal(3)),
],
},],
},
]
);
}
}

View file

@ -1,609 +0,0 @@
use std::collections::HashMap;
use anyhow::Result;
use serde::Serialize;
use thiserror::Error;
use super::{ExtractedLexicalGrammar, ExtractedSyntaxGrammar, InternedGrammar};
use crate::{
grammars::{ExternalToken, ReservedWordContext, Variable, VariableType},
rules::{MetadataParams, Rule, Symbol, SymbolType},
};
pub type ExtractTokensResult<T> = Result<T, ExtractTokensError>;
#[derive(Debug, Error, Serialize)]
pub enum ExtractTokensError {
#[error(
"The rule `{0}` contains an empty string.
Tree-sitter does not support syntactic rules that contain an empty string
unless they are used only as the grammar's start rule.
"
)]
EmptyString(String),
#[error("Rule '{0}' cannot be used as both an external token and a non-terminal rule")]
ExternalTokenNonTerminal(String),
#[error("Non-symbol rules cannot be used as external tokens")]
NonSymbolExternalToken,
#[error("Non-terminal symbol '{0}' cannot be used as the word token, because its rule is duplicated in '{1}'")]
NonTerminalWordToken(String, String),
#[error("Reserved words must be tokens")]
NonTokenReservedWord,
}
pub(super) fn extract_tokens(
mut grammar: InternedGrammar,
) -> ExtractTokensResult<(ExtractedSyntaxGrammar, ExtractedLexicalGrammar)> {
let mut extractor = TokenExtractor {
current_variable_name: String::new(),
current_variable_token_count: 0,
is_first_rule: false,
extracted_variables: Vec::new(),
extracted_usage_counts: Vec::new(),
};
for (i, variable) in &mut grammar.variables.iter_mut().enumerate() {
extractor.extract_tokens_in_variable(i == 0, variable)?;
}
for variable in &mut grammar.external_tokens {
extractor.extract_tokens_in_variable(false, variable)?;
}
let mut lexical_variables = Vec::with_capacity(extractor.extracted_variables.len());
for variable in extractor.extracted_variables {
lexical_variables.push(variable);
}
// If a variable's entire rule was extracted as a token and that token didn't
// appear within any other rule, then remove that variable from the syntax
// grammar, giving its name to the token in the lexical grammar. Any symbols
// that pointed to that variable will need to be updated to point to the
// variable in the lexical grammar. Symbols that pointed to later variables
// will need to have their indices decremented.
let mut variables = Vec::new();
let mut symbol_replacer = SymbolReplacer {
replacements: HashMap::new(),
};
for (i, variable) in grammar.variables.into_iter().enumerate() {
if let Rule::Symbol(Symbol {
kind: SymbolType::Terminal,
index,
}) = variable.rule
{
if i > 0 && extractor.extracted_usage_counts[index] == 1 {
let lexical_variable = &mut lexical_variables[index];
if lexical_variable.kind == VariableType::Auxiliary
|| variable.kind != VariableType::Hidden
{
lexical_variable.kind = variable.kind;
lexical_variable.name = variable.name;
symbol_replacer.replacements.insert(i, index);
continue;
}
}
}
variables.push(variable);
}
for variable in &mut variables {
variable.rule = symbol_replacer.replace_symbols_in_rule(&variable.rule);
}
let expected_conflicts = grammar
.expected_conflicts
.into_iter()
.map(|conflict| {
let mut result = conflict
.iter()
.map(|symbol| symbol_replacer.replace_symbol(*symbol))
.collect::<Vec<_>>();
result.sort_unstable();
result.dedup();
result
})
.collect();
let supertype_symbols = grammar
.supertype_symbols
.into_iter()
.map(|symbol| symbol_replacer.replace_symbol(symbol))
.collect();
let variables_to_inline = grammar
.variables_to_inline
.into_iter()
.map(|symbol| symbol_replacer.replace_symbol(symbol))
.collect();
let mut separators = Vec::new();
let mut extra_symbols = Vec::new();
for rule in grammar.extra_symbols {
if let Rule::Symbol(symbol) = rule {
extra_symbols.push(symbol_replacer.replace_symbol(symbol));
} else if let Some(index) = lexical_variables.iter().position(|v| v.rule == rule) {
extra_symbols.push(Symbol::terminal(index));
} else {
separators.push(rule);
}
}
let mut external_tokens = Vec::new();
for external_token in grammar.external_tokens {
let rule = symbol_replacer.replace_symbols_in_rule(&external_token.rule);
if let Rule::Symbol(symbol) = rule {
if symbol.is_non_terminal() {
Err(ExtractTokensError::ExternalTokenNonTerminal(
variables[symbol.index].name.clone(),
))?;
}
if symbol.is_external() {
external_tokens.push(ExternalToken {
name: external_token.name,
kind: external_token.kind,
corresponding_internal_token: None,
});
} else {
external_tokens.push(ExternalToken {
name: lexical_variables[symbol.index].name.clone(),
kind: external_token.kind,
corresponding_internal_token: Some(symbol),
});
}
} else {
Err(ExtractTokensError::NonSymbolExternalToken)?;
}
}
let mut word_token = None;
if let Some(token) = grammar.word_token {
let token = symbol_replacer.replace_symbol(token);
if token.is_non_terminal() {
let word_token_variable = &variables[token.index];
let conflicting_variable = variables
.iter()
.enumerate()
.find(|(i, v)| *i != token.index && v.rule == word_token_variable.rule)
.expect("Failed to find a variable with the same rule as the word token");
Err(ExtractTokensError::NonTerminalWordToken(
word_token_variable.name.clone(),
conflicting_variable.1.name.clone(),
))?;
}
word_token = Some(token);
}
let mut reserved_word_contexts = Vec::new();
for reserved_word_context in grammar.reserved_word_sets {
let mut reserved_words = Vec::new();
for reserved_rule in reserved_word_context.reserved_words {
if let Rule::Symbol(symbol) = reserved_rule {
reserved_words.push(symbol_replacer.replace_symbol(symbol));
} else if let Some(index) = lexical_variables
.iter()
.position(|v| v.rule == reserved_rule)
{
reserved_words.push(Symbol::terminal(index));
} else {
Err(ExtractTokensError::NonTokenReservedWord)?;
}
}
reserved_word_contexts.push(ReservedWordContext {
name: reserved_word_context.name,
reserved_words,
});
}
Ok((
ExtractedSyntaxGrammar {
variables,
expected_conflicts,
extra_symbols,
variables_to_inline,
supertype_symbols,
external_tokens,
word_token,
precedence_orderings: grammar.precedence_orderings,
reserved_word_sets: reserved_word_contexts,
},
ExtractedLexicalGrammar {
variables: lexical_variables,
separators,
},
))
}
struct TokenExtractor {
current_variable_name: String,
current_variable_token_count: usize,
is_first_rule: bool,
extracted_variables: Vec<Variable>,
extracted_usage_counts: Vec<usize>,
}
struct SymbolReplacer {
replacements: HashMap<usize, usize>,
}
impl TokenExtractor {
fn extract_tokens_in_variable(
&mut self,
is_first: bool,
variable: &mut Variable,
) -> ExtractTokensResult<()> {
self.current_variable_name.clear();
self.current_variable_name.push_str(&variable.name);
self.current_variable_token_count = 0;
self.is_first_rule = is_first;
variable.rule = self.extract_tokens_in_rule(&variable.rule)?;
Ok(())
}
fn extract_tokens_in_rule(&mut self, input: &Rule) -> ExtractTokensResult<Rule> {
match input {
Rule::String(name) => Ok(self.extract_token(input, Some(name))?.into()),
Rule::Pattern(..) => Ok(self.extract_token(input, None)?.into()),
Rule::Metadata { params, rule } => {
if params.is_token {
let mut params = params.clone();
params.is_token = false;
let mut string_value = None;
if let Rule::String(value) = rule.as_ref() {
string_value = Some(value);
}
let rule_to_extract = if params == MetadataParams::default() {
rule.as_ref()
} else {
input
};
Ok(self.extract_token(rule_to_extract, string_value)?.into())
} else {
Ok(Rule::Metadata {
params: params.clone(),
rule: Box::new(self.extract_tokens_in_rule(rule)?),
})
}
}
Rule::Repeat(content) => Ok(Rule::Repeat(Box::new(
self.extract_tokens_in_rule(content)?,
))),
Rule::Seq(elements) => Ok(Rule::Seq(
elements
.iter()
.map(|e| self.extract_tokens_in_rule(e))
.collect::<ExtractTokensResult<Vec<_>>>()?,
)),
Rule::Choice(elements) => Ok(Rule::Choice(
elements
.iter()
.map(|e| self.extract_tokens_in_rule(e))
.collect::<ExtractTokensResult<Vec<_>>>()?,
)),
Rule::Reserved { rule, context_name } => Ok(Rule::Reserved {
rule: Box::new(self.extract_tokens_in_rule(rule)?),
context_name: context_name.clone(),
}),
_ => Ok(input.clone()),
}
}
fn extract_token(
&mut self,
rule: &Rule,
string_value: Option<&String>,
) -> ExtractTokensResult<Symbol> {
for (i, variable) in self.extracted_variables.iter_mut().enumerate() {
if variable.rule == *rule {
self.extracted_usage_counts[i] += 1;
return Ok(Symbol::terminal(i));
}
}
let index = self.extracted_variables.len();
let variable = if let Some(string_value) = string_value {
if string_value.is_empty() && !self.is_first_rule {
Err(ExtractTokensError::EmptyString(
self.current_variable_name.clone(),
))?;
}
Variable {
name: string_value.clone(),
kind: VariableType::Anonymous,
rule: rule.clone(),
}
} else {
self.current_variable_token_count += 1;
Variable {
name: format!(
"{}_token{}",
self.current_variable_name, self.current_variable_token_count
),
kind: VariableType::Auxiliary,
rule: rule.clone(),
}
};
self.extracted_variables.push(variable);
self.extracted_usage_counts.push(1);
Ok(Symbol::terminal(index))
}
}
impl SymbolReplacer {
fn replace_symbols_in_rule(&mut self, rule: &Rule) -> Rule {
match rule {
Rule::Symbol(symbol) => self.replace_symbol(*symbol).into(),
Rule::Choice(elements) => Rule::Choice(
elements
.iter()
.map(|e| self.replace_symbols_in_rule(e))
.collect(),
),
Rule::Seq(elements) => Rule::Seq(
elements
.iter()
.map(|e| self.replace_symbols_in_rule(e))
.collect(),
),
Rule::Repeat(content) => Rule::Repeat(Box::new(self.replace_symbols_in_rule(content))),
Rule::Metadata { rule, params } => Rule::Metadata {
params: params.clone(),
rule: Box::new(self.replace_symbols_in_rule(rule)),
},
Rule::Reserved { rule, context_name } => Rule::Reserved {
rule: Box::new(self.replace_symbols_in_rule(rule)),
context_name: context_name.clone(),
},
_ => rule.clone(),
}
}
fn replace_symbol(&self, symbol: Symbol) -> Symbol {
if !symbol.is_non_terminal() {
return symbol;
}
if let Some(replacement) = self.replacements.get(&symbol.index) {
return Symbol::terminal(*replacement);
}
let mut adjusted_index = symbol.index;
for replaced_index in self.replacements.keys() {
if *replaced_index < symbol.index {
adjusted_index -= 1;
}
}
Symbol::non_terminal(adjusted_index)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_extraction() {
let (syntax_grammar, lexical_grammar) = extract_tokens(build_grammar(vec![
Variable::named(
"rule_0",
Rule::repeat(Rule::seq(vec![
Rule::string("a"),
Rule::pattern("b", ""),
Rule::choice(vec![
Rule::non_terminal(1),
Rule::non_terminal(2),
Rule::token(Rule::repeat(Rule::choice(vec![
Rule::string("c"),
Rule::string("d"),
]))),
]),
])),
),
Variable::named("rule_1", Rule::pattern("e", "")),
Variable::named("rule_2", Rule::pattern("b", "")),
Variable::named(
"rule_3",
Rule::seq(vec![Rule::non_terminal(2), Rule::Blank]),
),
]))
.unwrap();
assert_eq!(
syntax_grammar.variables,
vec![
Variable::named(
"rule_0",
Rule::repeat(Rule::seq(vec![
// The string "a" was replaced by a symbol referencing the lexical grammar
Rule::terminal(0),
// The pattern "b" was replaced by a symbol referencing the lexical grammar
Rule::terminal(1),
Rule::choice(vec![
// The symbol referencing `rule_1` was replaced by a symbol referencing
// the lexical grammar.
Rule::terminal(3),
// The symbol referencing `rule_2` had its index decremented because
// `rule_1` was moved to the lexical grammar.
Rule::non_terminal(1),
// The rule wrapped in `token` was replaced by a symbol referencing
// the lexical grammar.
Rule::terminal(2),
])
]))
),
// The pattern "e" was only used in once place: as the definition of `rule_1`,
// so that rule was moved to the lexical grammar. The pattern "b" appeared in
// two places, so it was not moved into the lexical grammar.
Variable::named("rule_2", Rule::terminal(1)),
Variable::named(
"rule_3",
Rule::seq(vec![Rule::non_terminal(1), Rule::Blank,])
),
]
);
assert_eq!(
lexical_grammar.variables,
vec![
Variable::anonymous("a", Rule::string("a")),
Variable::auxiliary("rule_0_token1", Rule::pattern("b", "")),
Variable::auxiliary(
"rule_0_token2",
Rule::repeat(Rule::choice(vec![Rule::string("c"), Rule::string("d"),]))
),
Variable::named("rule_1", Rule::pattern("e", "")),
]
);
}
#[test]
fn test_start_rule_is_token() {
let (syntax_grammar, lexical_grammar) =
extract_tokens(build_grammar(vec![Variable::named(
"rule_0",
Rule::string("hello"),
)]))
.unwrap();
assert_eq!(
syntax_grammar.variables,
vec![Variable::named("rule_0", Rule::terminal(0)),]
);
assert_eq!(
lexical_grammar.variables,
vec![Variable::anonymous("hello", Rule::string("hello")),]
);
}
#[test]
fn test_extracting_extra_symbols() {
let mut grammar = build_grammar(vec![
Variable::named("rule_0", Rule::string("x")),
Variable::named("comment", Rule::pattern("//.*", "")),
]);
grammar.extra_symbols = vec![Rule::string(" "), Rule::non_terminal(1)];
let (syntax_grammar, lexical_grammar) = extract_tokens(grammar).unwrap();
assert_eq!(syntax_grammar.extra_symbols, vec![Symbol::terminal(1),]);
assert_eq!(lexical_grammar.separators, vec![Rule::string(" "),]);
}
#[test]
fn test_extract_externals() {
let mut grammar = build_grammar(vec![
Variable::named(
"rule_0",
Rule::seq(vec![
Rule::external(0),
Rule::string("a"),
Rule::non_terminal(1),
Rule::non_terminal(2),
]),
),
Variable::named("rule_1", Rule::string("b")),
Variable::named("rule_2", Rule::string("c")),
]);
grammar.external_tokens = vec![
Variable::named("external_0", Rule::external(0)),
Variable::anonymous("a", Rule::string("a")),
Variable::named("rule_2", Rule::non_terminal(2)),
];
let (syntax_grammar, _) = extract_tokens(grammar).unwrap();
assert_eq!(
syntax_grammar.external_tokens,
vec![
ExternalToken {
name: "external_0".to_string(),
kind: VariableType::Named,
corresponding_internal_token: None,
},
ExternalToken {
name: "a".to_string(),
kind: VariableType::Anonymous,
corresponding_internal_token: Some(Symbol::terminal(0)),
},
ExternalToken {
name: "rule_2".to_string(),
kind: VariableType::Named,
corresponding_internal_token: Some(Symbol::terminal(2)),
},
]
);
}
#[test]
fn test_error_on_external_with_same_name_as_non_terminal() {
let mut grammar = build_grammar(vec![
Variable::named(
"rule_0",
Rule::seq(vec![Rule::non_terminal(1), Rule::non_terminal(2)]),
),
Variable::named(
"rule_1",
Rule::seq(vec![Rule::non_terminal(2), Rule::non_terminal(2)]),
),
Variable::named("rule_2", Rule::string("a")),
]);
grammar.external_tokens = vec![Variable::named("rule_1", Rule::non_terminal(1))];
match extract_tokens(grammar) {
Err(e) => {
assert_eq!(e.to_string(), "Rule 'rule_1' cannot be used as both an external token and a non-terminal rule");
}
_ => {
panic!("Expected an error but got no error");
}
}
}
#[test]
fn test_extraction_on_hidden_terminal() {
let (syntax_grammar, lexical_grammar) = extract_tokens(build_grammar(vec![
Variable::named("rule_0", Rule::non_terminal(1)),
Variable::hidden("_rule_1", Rule::string("a")),
]))
.unwrap();
// The rule `_rule_1` should not "absorb" the
// terminal "a", since it is hidden,
// so we expect two variables still
assert_eq!(
syntax_grammar.variables,
vec![
Variable::named("rule_0", Rule::non_terminal(1)),
Variable::hidden("_rule_1", Rule::terminal(0)),
]
);
// We should not have a hidden rule in our lexical grammar, only the terminal "a"
assert_eq!(
lexical_grammar.variables,
vec![Variable::anonymous("a", Rule::string("a"))]
);
}
#[test]
fn test_extraction_with_empty_string() {
assert!(extract_tokens(build_grammar(vec![
Variable::named("rule_0", Rule::non_terminal(1)),
Variable::hidden("_rule_1", Rule::string("")),
]))
.is_err());
}
fn build_grammar(variables: Vec<Variable>) -> InternedGrammar {
InternedGrammar {
variables,
..Default::default()
}
}
}

View file

@ -1,539 +0,0 @@
use std::collections::HashMap;
use anyhow::Result;
use serde::Serialize;
use thiserror::Error;
use super::ExtractedSyntaxGrammar;
use crate::{
grammars::{
Production, ProductionStep, ReservedWordSetId, SyntaxGrammar, SyntaxVariable, Variable,
},
rules::{Alias, Associativity, Precedence, Rule, Symbol, TokenSet},
};
pub type FlattenGrammarResult<T> = Result<T, FlattenGrammarError>;
#[derive(Debug, Error, Serialize)]
pub enum FlattenGrammarError {
#[error("No such reserved word set: {0}")]
NoReservedWordSet(String),
#[error(
"The rule `{0}` matches the empty string.
Tree-sitter does not support syntactic rules that match the empty string
unless they are used only as the grammar's start rule.
"
)]
EmptyString(String),
#[error("Rule `{0}` cannot be inlined because it contains a reference to itself")]
RecursiveInline(String),
}
struct RuleFlattener {
production: Production,
reserved_word_set_ids: HashMap<String, ReservedWordSetId>,
precedence_stack: Vec<Precedence>,
associativity_stack: Vec<Associativity>,
reserved_word_stack: Vec<ReservedWordSetId>,
alias_stack: Vec<Alias>,
field_name_stack: Vec<String>,
}
impl RuleFlattener {
const fn new(reserved_word_set_ids: HashMap<String, ReservedWordSetId>) -> Self {
Self {
production: Production {
steps: Vec::new(),
dynamic_precedence: 0,
},
reserved_word_set_ids,
precedence_stack: Vec::new(),
associativity_stack: Vec::new(),
reserved_word_stack: Vec::new(),
alias_stack: Vec::new(),
field_name_stack: Vec::new(),
}
}
fn flatten_variable(&mut self, variable: Variable) -> FlattenGrammarResult<SyntaxVariable> {
let mut productions = Vec::new();
for rule in extract_choices(variable.rule) {
let production = self.flatten_rule(rule)?;
if !productions.contains(&production) {
productions.push(production);
}
}
Ok(SyntaxVariable {
name: variable.name,
kind: variable.kind,
productions,
})
}
fn flatten_rule(&mut self, rule: Rule) -> FlattenGrammarResult<Production> {
self.production = Production::default();
self.alias_stack.clear();
self.reserved_word_stack.clear();
self.precedence_stack.clear();
self.associativity_stack.clear();
self.field_name_stack.clear();
self.apply(rule, true)?;
Ok(self.production.clone())
}
fn apply(&mut self, rule: Rule, at_end: bool) -> FlattenGrammarResult<bool> {
match rule {
Rule::Seq(members) => {
let mut result = false;
let last_index = members.len() - 1;
for (i, member) in members.into_iter().enumerate() {
result |= self.apply(member, i == last_index && at_end)?;
}
Ok(result)
}
Rule::Metadata { rule, params } => {
let mut has_precedence = false;
if !params.precedence.is_none() {
has_precedence = true;
self.precedence_stack.push(params.precedence);
}
let mut has_associativity = false;
if let Some(associativity) = params.associativity {
has_associativity = true;
self.associativity_stack.push(associativity);
}
let mut has_alias = false;
if let Some(alias) = params.alias {
has_alias = true;
self.alias_stack.push(alias);
}
let mut has_field_name = false;
if let Some(field_name) = params.field_name {
has_field_name = true;
self.field_name_stack.push(field_name);
}
if params.dynamic_precedence.abs() > self.production.dynamic_precedence.abs() {
self.production.dynamic_precedence = params.dynamic_precedence;
}
let did_push = self.apply(*rule, at_end)?;
if has_precedence {
self.precedence_stack.pop();
if did_push && !at_end {
self.production.steps.last_mut().unwrap().precedence = self
.precedence_stack
.last()
.cloned()
.unwrap_or(Precedence::None);
}
}
if has_associativity {
self.associativity_stack.pop();
if did_push && !at_end {
self.production.steps.last_mut().unwrap().associativity =
self.associativity_stack.last().copied();
}
}
if has_alias {
self.alias_stack.pop();
}
if has_field_name {
self.field_name_stack.pop();
}
Ok(did_push)
}
Rule::Reserved { rule, context_name } => {
self.reserved_word_stack.push(
self.reserved_word_set_ids
.get(&context_name)
.copied()
.ok_or_else(|| {
FlattenGrammarError::NoReservedWordSet(context_name.clone())
})?,
);
let did_push = self.apply(*rule, at_end)?;
self.reserved_word_stack.pop();
Ok(did_push)
}
Rule::Symbol(symbol) => {
self.production.steps.push(ProductionStep {
symbol,
precedence: self
.precedence_stack
.last()
.cloned()
.unwrap_or(Precedence::None),
associativity: self.associativity_stack.last().copied(),
reserved_word_set_id: self
.reserved_word_stack
.last()
.copied()
.unwrap_or(ReservedWordSetId::default()),
alias: self.alias_stack.last().cloned(),
field_name: self.field_name_stack.last().cloned(),
});
Ok(true)
}
_ => Ok(false),
}
}
}
fn extract_choices(rule: Rule) -> Vec<Rule> {
match rule {
Rule::Seq(elements) => {
let mut result = vec![Rule::Blank];
for element in elements {
let extraction = extract_choices(element);
let mut next_result = Vec::new();
for entry in result {
for extraction_entry in &extraction {
next_result.push(Rule::Seq(vec![entry.clone(), extraction_entry.clone()]));
}
}
result = next_result;
}
result
}
Rule::Choice(elements) => {
let mut result = Vec::new();
for element in elements {
for rule in extract_choices(element) {
result.push(rule);
}
}
result
}
Rule::Metadata { rule, params } => extract_choices(*rule)
.into_iter()
.map(|rule| Rule::Metadata {
rule: Box::new(rule),
params: params.clone(),
})
.collect(),
Rule::Reserved { rule, context_name } => extract_choices(*rule)
.into_iter()
.map(|rule| Rule::Reserved {
rule: Box::new(rule),
context_name: context_name.clone(),
})
.collect(),
_ => vec![rule],
}
}
fn symbol_is_used(variables: &[SyntaxVariable], symbol: Symbol) -> bool {
for variable in variables {
for production in &variable.productions {
for step in &production.steps {
if step.symbol == symbol {
return true;
}
}
}
}
false
}
pub(super) fn flatten_grammar(
grammar: ExtractedSyntaxGrammar,
) -> FlattenGrammarResult<SyntaxGrammar> {
let mut reserved_word_set_ids_by_name = HashMap::new();
for (ix, set) in grammar.reserved_word_sets.iter().enumerate() {
reserved_word_set_ids_by_name.insert(set.name.clone(), ReservedWordSetId(ix));
}
let mut flattener = RuleFlattener::new(reserved_word_set_ids_by_name);
let variables = grammar
.variables
.into_iter()
.map(|variable| flattener.flatten_variable(variable))
.collect::<FlattenGrammarResult<Vec<_>>>()?;
for (i, variable) in variables.iter().enumerate() {
let symbol = Symbol::non_terminal(i);
for production in &variable.productions {
if production.steps.is_empty() && symbol_is_used(&variables, symbol) {
Err(FlattenGrammarError::EmptyString(variable.name.clone()))?;
}
if grammar.variables_to_inline.contains(&symbol)
&& production.steps.iter().any(|step| step.symbol == symbol)
{
Err(FlattenGrammarError::RecursiveInline(variable.name.clone()))?;
}
}
}
let mut reserved_word_sets = grammar
.reserved_word_sets
.into_iter()
.map(|set| set.reserved_words.into_iter().collect())
.collect::<Vec<_>>();
// If no default reserved word set is specified, there are no reserved words.
if reserved_word_sets.is_empty() {
reserved_word_sets.push(TokenSet::default());
}
Ok(SyntaxGrammar {
extra_symbols: grammar.extra_symbols,
expected_conflicts: grammar.expected_conflicts,
variables_to_inline: grammar.variables_to_inline,
precedence_orderings: grammar.precedence_orderings,
external_tokens: grammar.external_tokens,
supertype_symbols: grammar.supertype_symbols,
word_token: grammar.word_token,
reserved_word_sets,
variables,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::grammars::VariableType;
#[test]
fn test_flatten_grammar() {
let mut flattener = RuleFlattener::new(HashMap::default());
let result = flattener
.flatten_variable(Variable {
name: "test".to_string(),
kind: VariableType::Named,
rule: Rule::seq(vec![
Rule::non_terminal(1),
Rule::prec_left(
Precedence::Integer(101),
Rule::seq(vec![
Rule::non_terminal(2),
Rule::choice(vec![
Rule::prec_right(
Precedence::Integer(102),
Rule::seq(vec![Rule::non_terminal(3), Rule::non_terminal(4)]),
),
Rule::non_terminal(5),
]),
Rule::non_terminal(6),
]),
),
Rule::non_terminal(7),
]),
})
.unwrap();
assert_eq!(
result.productions,
vec![
Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::non_terminal(1)),
ProductionStep::new(Symbol::non_terminal(2))
.with_prec(Precedence::Integer(101), Some(Associativity::Left)),
ProductionStep::new(Symbol::non_terminal(3))
.with_prec(Precedence::Integer(102), Some(Associativity::Right)),
ProductionStep::new(Symbol::non_terminal(4))
.with_prec(Precedence::Integer(101), Some(Associativity::Left)),
ProductionStep::new(Symbol::non_terminal(6)),
ProductionStep::new(Symbol::non_terminal(7)),
]
},
Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::non_terminal(1)),
ProductionStep::new(Symbol::non_terminal(2))
.with_prec(Precedence::Integer(101), Some(Associativity::Left)),
ProductionStep::new(Symbol::non_terminal(5))
.with_prec(Precedence::Integer(101), Some(Associativity::Left)),
ProductionStep::new(Symbol::non_terminal(6)),
ProductionStep::new(Symbol::non_terminal(7)),
]
},
]
);
}
#[test]
fn test_flatten_grammar_with_maximum_dynamic_precedence() {
let mut flattener = RuleFlattener::new(HashMap::default());
let result = flattener
.flatten_variable(Variable {
name: "test".to_string(),
kind: VariableType::Named,
rule: Rule::seq(vec![
Rule::non_terminal(1),
Rule::prec_dynamic(
101,
Rule::seq(vec![
Rule::non_terminal(2),
Rule::choice(vec![
Rule::prec_dynamic(
102,
Rule::seq(vec![Rule::non_terminal(3), Rule::non_terminal(4)]),
),
Rule::non_terminal(5),
]),
Rule::non_terminal(6),
]),
),
Rule::non_terminal(7),
]),
})
.unwrap();
assert_eq!(
result.productions,
vec![
Production {
dynamic_precedence: 102,
steps: vec![
ProductionStep::new(Symbol::non_terminal(1)),
ProductionStep::new(Symbol::non_terminal(2)),
ProductionStep::new(Symbol::non_terminal(3)),
ProductionStep::new(Symbol::non_terminal(4)),
ProductionStep::new(Symbol::non_terminal(6)),
ProductionStep::new(Symbol::non_terminal(7)),
],
},
Production {
dynamic_precedence: 101,
steps: vec![
ProductionStep::new(Symbol::non_terminal(1)),
ProductionStep::new(Symbol::non_terminal(2)),
ProductionStep::new(Symbol::non_terminal(5)),
ProductionStep::new(Symbol::non_terminal(6)),
ProductionStep::new(Symbol::non_terminal(7)),
],
},
]
);
}
#[test]
fn test_flatten_grammar_with_final_precedence() {
let mut flattener = RuleFlattener::new(HashMap::default());
let result = flattener
.flatten_variable(Variable {
name: "test".to_string(),
kind: VariableType::Named,
rule: Rule::prec_left(
Precedence::Integer(101),
Rule::seq(vec![Rule::non_terminal(1), Rule::non_terminal(2)]),
),
})
.unwrap();
assert_eq!(
result.productions,
vec![Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::non_terminal(1))
.with_prec(Precedence::Integer(101), Some(Associativity::Left)),
ProductionStep::new(Symbol::non_terminal(2))
.with_prec(Precedence::Integer(101), Some(Associativity::Left)),
]
}]
);
let result = flattener
.flatten_variable(Variable {
name: "test".to_string(),
kind: VariableType::Named,
rule: Rule::prec_left(
Precedence::Integer(101),
Rule::seq(vec![Rule::non_terminal(1)]),
),
})
.unwrap();
assert_eq!(
result.productions,
vec![Production {
dynamic_precedence: 0,
steps: vec![ProductionStep::new(Symbol::non_terminal(1))
.with_prec(Precedence::Integer(101), Some(Associativity::Left)),]
}]
);
}
#[test]
fn test_flatten_grammar_with_field_names() {
let mut flattener = RuleFlattener::new(HashMap::default());
let result = flattener
.flatten_variable(Variable {
name: "test".to_string(),
kind: VariableType::Named,
rule: Rule::seq(vec![
Rule::field("first-thing".to_string(), Rule::terminal(1)),
Rule::terminal(2),
Rule::choice(vec![
Rule::Blank,
Rule::field("second-thing".to_string(), Rule::terminal(3)),
]),
]),
})
.unwrap();
assert_eq!(
result.productions,
vec![
Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(1)).with_field_name("first-thing"),
ProductionStep::new(Symbol::terminal(2))
]
},
Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(1)).with_field_name("first-thing"),
ProductionStep::new(Symbol::terminal(2)),
ProductionStep::new(Symbol::terminal(3)).with_field_name("second-thing"),
]
},
]
);
}
#[test]
fn test_flatten_grammar_with_recursive_inline_variable() {
let result = flatten_grammar(ExtractedSyntaxGrammar {
extra_symbols: Vec::new(),
expected_conflicts: Vec::new(),
variables_to_inline: vec![Symbol::non_terminal(0)],
precedence_orderings: Vec::new(),
external_tokens: Vec::new(),
supertype_symbols: Vec::new(),
word_token: None,
reserved_word_sets: Vec::new(),
variables: vec![Variable {
name: "test".to_string(),
kind: VariableType::Named,
rule: Rule::seq(vec![
Rule::non_terminal(0),
Rule::non_terminal(1),
Rule::non_terminal(2),
]),
}],
});
assert_eq!(
result.unwrap_err().to_string(),
"Rule `test` cannot be inlined because it contains a reference to itself.",
);
}
}

View file

@ -1,293 +0,0 @@
use anyhow::Result;
use serde::Serialize;
use thiserror::Error;
use super::InternedGrammar;
use crate::{
grammars::{InputGrammar, ReservedWordContext, Variable, VariableType},
rules::{Rule, Symbol},
};
pub type InternSymbolsResult<T> = Result<T, InternSymbolsError>;
#[derive(Debug, Error, Serialize)]
pub enum InternSymbolsError {
#[error("A grammar's start rule must be visible.")]
HiddenStartRule,
#[error("Undefined symbol `{0}`")]
Undefined(String),
#[error("Undefined symbol `{0}` in grammar's supertypes array")]
UndefinedSupertype(String),
#[error("Undefined symbol `{0}` in grammar's conflicts array")]
UndefinedConflict(String),
#[error("Undefined symbol `{0}` as grammar's word token")]
UndefinedWordToken(String),
}
pub(super) fn intern_symbols(grammar: &InputGrammar) -> InternSymbolsResult<InternedGrammar> {
let interner = Interner { grammar };
if variable_type_for_name(&grammar.variables[0].name) == VariableType::Hidden {
Err(InternSymbolsError::HiddenStartRule)?;
}
let mut variables = Vec::with_capacity(grammar.variables.len());
for variable in &grammar.variables {
variables.push(Variable {
name: variable.name.clone(),
kind: variable_type_for_name(&variable.name),
rule: interner.intern_rule(&variable.rule, Some(&variable.name))?,
});
}
let mut external_tokens = Vec::with_capacity(grammar.external_tokens.len());
for external_token in &grammar.external_tokens {
let rule = interner.intern_rule(external_token, None)?;
let (name, kind) = if let Rule::NamedSymbol(name) = external_token {
(name.clone(), variable_type_for_name(name))
} else {
(String::new(), VariableType::Anonymous)
};
external_tokens.push(Variable { name, kind, rule });
}
let mut extra_symbols = Vec::with_capacity(grammar.extra_symbols.len());
for extra_token in &grammar.extra_symbols {
extra_symbols.push(interner.intern_rule(extra_token, None)?);
}
let mut supertype_symbols = Vec::with_capacity(grammar.supertype_symbols.len());
for supertype_symbol_name in &grammar.supertype_symbols {
supertype_symbols.push(interner.intern_name(supertype_symbol_name).ok_or_else(|| {
InternSymbolsError::UndefinedSupertype(supertype_symbol_name.clone())
})?);
}
let mut reserved_words = Vec::with_capacity(grammar.reserved_words.len());
for reserved_word_set in &grammar.reserved_words {
let mut interned_set = Vec::new();
for rule in &reserved_word_set.reserved_words {
interned_set.push(interner.intern_rule(rule, None)?);
}
reserved_words.push(ReservedWordContext {
name: reserved_word_set.name.clone(),
reserved_words: interned_set,
});
}
let mut expected_conflicts = Vec::new();
for conflict in &grammar.expected_conflicts {
let mut interned_conflict = Vec::with_capacity(conflict.len());
for name in conflict {
interned_conflict.push(
interner
.intern_name(name)
.ok_or_else(|| InternSymbolsError::UndefinedConflict(name.clone()))?,
);
}
expected_conflicts.push(interned_conflict);
}
let mut variables_to_inline = Vec::new();
for name in &grammar.variables_to_inline {
if let Some(symbol) = interner.intern_name(name) {
variables_to_inline.push(symbol);
}
}
let mut word_token = None;
if let Some(name) = grammar.word_token.as_ref() {
word_token = Some(
interner
.intern_name(name)
.ok_or_else(|| InternSymbolsError::UndefinedWordToken(name.clone()))?,
);
}
for (i, variable) in variables.iter_mut().enumerate() {
if supertype_symbols.contains(&Symbol::non_terminal(i)) {
variable.kind = VariableType::Hidden;
}
}
Ok(InternedGrammar {
variables,
external_tokens,
extra_symbols,
expected_conflicts,
variables_to_inline,
supertype_symbols,
word_token,
precedence_orderings: grammar.precedence_orderings.clone(),
reserved_word_sets: reserved_words,
})
}
struct Interner<'a> {
grammar: &'a InputGrammar,
}
impl Interner<'_> {
fn intern_rule(&self, rule: &Rule, name: Option<&str>) -> InternSymbolsResult<Rule> {
match rule {
Rule::Choice(elements) => {
self.check_single(elements, name);
let mut result = Vec::with_capacity(elements.len());
for element in elements {
result.push(self.intern_rule(element, name)?);
}
Ok(Rule::Choice(result))
}
Rule::Seq(elements) => {
self.check_single(elements, name);
let mut result = Vec::with_capacity(elements.len());
for element in elements {
result.push(self.intern_rule(element, name)?);
}
Ok(Rule::Seq(result))
}
Rule::Repeat(content) => Ok(Rule::Repeat(Box::new(self.intern_rule(content, name)?))),
Rule::Metadata { rule, params } => Ok(Rule::Metadata {
rule: Box::new(self.intern_rule(rule, name)?),
params: params.clone(),
}),
Rule::Reserved { rule, context_name } => Ok(Rule::Reserved {
rule: Box::new(self.intern_rule(rule, name)?),
context_name: context_name.clone(),
}),
Rule::NamedSymbol(name) => self.intern_name(name).map_or_else(
|| Err(InternSymbolsError::Undefined(name.clone())),
|symbol| Ok(Rule::Symbol(symbol)),
),
_ => Ok(rule.clone()),
}
}
fn intern_name(&self, symbol: &str) -> Option<Symbol> {
for (i, variable) in self.grammar.variables.iter().enumerate() {
if variable.name == symbol {
return Some(Symbol::non_terminal(i));
}
}
for (i, external_token) in self.grammar.external_tokens.iter().enumerate() {
if let Rule::NamedSymbol(name) = external_token {
if name == symbol {
return Some(Symbol::external(i));
}
}
}
None
}
// In the case of a seq or choice rule of 1 element in a hidden rule, weird
// inconsistent behavior with queries can occur. So we should warn the user about it.
fn check_single(&self, elements: &[Rule], name: Option<&str>) {
if elements.len() == 1 && matches!(elements[0], Rule::String(_) | Rule::Pattern(_, _)) {
eprintln!(
"Warning: rule {} contains a `seq` or `choice` rule with a single element. This is unnecessary.",
name.unwrap_or_default()
);
}
}
}
fn variable_type_for_name(name: &str) -> VariableType {
if name.starts_with('_') {
VariableType::Hidden
} else {
VariableType::Named
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_repeat_expansion() {
let grammar = intern_symbols(&build_grammar(vec![
Variable::named("x", Rule::choice(vec![Rule::named("y"), Rule::named("_z")])),
Variable::named("y", Rule::named("_z")),
Variable::named("_z", Rule::string("a")),
]))
.unwrap();
assert_eq!(
grammar.variables,
vec![
Variable::named(
"x",
Rule::choice(vec![Rule::non_terminal(1), Rule::non_terminal(2),])
),
Variable::named("y", Rule::non_terminal(2)),
Variable::hidden("_z", Rule::string("a")),
]
);
}
#[test]
fn test_interning_external_token_names() {
// Variable `y` is both an internal and an external token.
// Variable `z` is just an external token.
let mut input_grammar = build_grammar(vec![
Variable::named(
"w",
Rule::choice(vec![Rule::named("x"), Rule::named("y"), Rule::named("z")]),
),
Variable::named("x", Rule::string("a")),
Variable::named("y", Rule::string("b")),
]);
input_grammar
.external_tokens
.extend(vec![Rule::named("y"), Rule::named("z")]);
let grammar = intern_symbols(&input_grammar).unwrap();
// Variable `y` is referred to by its internal index.
// Variable `z` is referred to by its external index.
assert_eq!(
grammar.variables,
vec![
Variable::named(
"w",
Rule::choice(vec![
Rule::non_terminal(1),
Rule::non_terminal(2),
Rule::external(1),
])
),
Variable::named("x", Rule::string("a")),
Variable::named("y", Rule::string("b")),
]
);
// The external token for `y` refers back to its internal index.
assert_eq!(
grammar.external_tokens,
vec![
Variable::named("y", Rule::non_terminal(2)),
Variable::named("z", Rule::external(1)),
]
);
}
#[test]
fn test_grammar_with_undefined_symbols() {
let result = intern_symbols(&build_grammar(vec![Variable::named("x", Rule::named("y"))]));
match result {
Err(e) => assert_eq!(e.to_string(), "Undefined symbol `y`"),
_ => panic!("Expected an error but got none"),
}
}
fn build_grammar(variables: Vec<Variable>) -> InputGrammar {
InputGrammar {
variables,
name: "the_language".to_string(),
..Default::default()
}
}
}

View file

@ -1,325 +0,0 @@
mod expand_repeats;
mod expand_tokens;
mod extract_default_aliases;
mod extract_tokens;
mod flatten_grammar;
mod intern_symbols;
mod process_inlines;
use std::{
cmp::Ordering,
collections::{hash_map, HashMap, HashSet},
mem,
};
use anyhow::Result;
pub use expand_tokens::ExpandTokensError;
pub use extract_tokens::ExtractTokensError;
pub use flatten_grammar::FlattenGrammarError;
pub use intern_symbols::InternSymbolsError;
pub use process_inlines::ProcessInlinesError;
use serde::Serialize;
use thiserror::Error;
pub use self::expand_tokens::expand_tokens;
use self::{
expand_repeats::expand_repeats, extract_default_aliases::extract_default_aliases,
extract_tokens::extract_tokens, flatten_grammar::flatten_grammar,
intern_symbols::intern_symbols, process_inlines::process_inlines,
};
use super::{
grammars::{
ExternalToken, InlinedProductionMap, InputGrammar, LexicalGrammar, PrecedenceEntry,
SyntaxGrammar, Variable,
},
rules::{AliasMap, Precedence, Rule, Symbol},
};
use crate::grammars::ReservedWordContext;
pub struct IntermediateGrammar<T, U> {
variables: Vec<Variable>,
extra_symbols: Vec<T>,
expected_conflicts: Vec<Vec<Symbol>>,
precedence_orderings: Vec<Vec<PrecedenceEntry>>,
external_tokens: Vec<U>,
variables_to_inline: Vec<Symbol>,
supertype_symbols: Vec<Symbol>,
word_token: Option<Symbol>,
reserved_word_sets: Vec<ReservedWordContext<T>>,
}
pub type InternedGrammar = IntermediateGrammar<Rule, Variable>;
pub type ExtractedSyntaxGrammar = IntermediateGrammar<Symbol, ExternalToken>;
#[derive(Debug, PartialEq, Eq)]
pub struct ExtractedLexicalGrammar {
pub variables: Vec<Variable>,
pub separators: Vec<Rule>,
}
impl<T, U> Default for IntermediateGrammar<T, U> {
fn default() -> Self {
Self {
variables: Vec::default(),
extra_symbols: Vec::default(),
expected_conflicts: Vec::default(),
precedence_orderings: Vec::default(),
external_tokens: Vec::default(),
variables_to_inline: Vec::default(),
supertype_symbols: Vec::default(),
word_token: Option::default(),
reserved_word_sets: Vec::default(),
}
}
}
pub type PrepareGrammarResult<T> = Result<T, PrepareGrammarError>;
#[derive(Debug, Error, Serialize)]
#[error(transparent)]
pub enum PrepareGrammarError {
ValidatePrecedences(#[from] ValidatePrecedenceError),
InternSymbols(#[from] InternSymbolsError),
ExtractTokens(#[from] ExtractTokensError),
FlattenGrammar(#[from] FlattenGrammarError),
ExpandTokens(#[from] ExpandTokensError),
ProcessInlines(#[from] ProcessInlinesError),
}
pub type ValidatePrecedenceResult<T> = Result<T, ValidatePrecedenceError>;
#[derive(Debug, Error, Serialize)]
#[error(transparent)]
pub enum ValidatePrecedenceError {
Undeclared(#[from] UndeclaredPrecedenceError),
Ordering(#[from] ConflictingPrecedenceOrderingError),
}
#[derive(Debug, Error, Serialize)]
pub struct UndeclaredPrecedenceError {
pub precedence: String,
pub rule: String,
}
impl std::fmt::Display for UndeclaredPrecedenceError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Undeclared precedence '{}' in rule '{}'",
self.precedence, self.rule
)?;
Ok(())
}
}
#[derive(Debug, Error, Serialize)]
pub struct ConflictingPrecedenceOrderingError {
pub precedence_1: String,
pub precedence_2: String,
}
impl std::fmt::Display for ConflictingPrecedenceOrderingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Conflicting orderings for precedences {} and {}",
self.precedence_1, self.precedence_2
)?;
Ok(())
}
}
/// Transform an input grammar into separate components that are ready
/// for parse table construction.
pub fn prepare_grammar(
input_grammar: &InputGrammar,
) -> PrepareGrammarResult<(
SyntaxGrammar,
LexicalGrammar,
InlinedProductionMap,
AliasMap,
)> {
validate_precedences(input_grammar)?;
let interned_grammar = intern_symbols(input_grammar)?;
let (syntax_grammar, lexical_grammar) = extract_tokens(interned_grammar)?;
let syntax_grammar = expand_repeats(syntax_grammar);
let mut syntax_grammar = flatten_grammar(syntax_grammar)?;
let lexical_grammar = expand_tokens(lexical_grammar)?;
let default_aliases = extract_default_aliases(&mut syntax_grammar, &lexical_grammar);
let inlines = process_inlines(&syntax_grammar, &lexical_grammar)?;
Ok((syntax_grammar, lexical_grammar, inlines, default_aliases))
}
/// Check that all of the named precedences used in the grammar are declared
/// within the `precedences` lists, and also that there are no conflicting
/// precedence orderings declared in those lists.
fn validate_precedences(grammar: &InputGrammar) -> ValidatePrecedenceResult<()> {
// Check that no rule contains a named precedence that is not present in
// any of the `precedences` lists.
fn validate(
rule_name: &str,
rule: &Rule,
names: &HashSet<&String>,
) -> ValidatePrecedenceResult<()> {
match rule {
Rule::Repeat(rule) => validate(rule_name, rule, names),
Rule::Seq(elements) | Rule::Choice(elements) => elements
.iter()
.try_for_each(|e| validate(rule_name, e, names)),
Rule::Metadata { rule, params } => {
if let Precedence::Name(n) = &params.precedence {
if !names.contains(n) {
Err(UndeclaredPrecedenceError {
precedence: n.to_string(),
rule: rule_name.to_string(),
})?;
}
}
validate(rule_name, rule, names)?;
Ok(())
}
_ => Ok(()),
}
}
// For any two precedence names `a` and `b`, if `a` comes before `b`
// in some list, then it cannot come *after* `b` in any list.
let mut pairs = HashMap::new();
for list in &grammar.precedence_orderings {
for (i, mut entry1) in list.iter().enumerate() {
for mut entry2 in list.iter().skip(i + 1) {
if entry2 == entry1 {
continue;
}
let mut ordering = Ordering::Greater;
if entry1 > entry2 {
ordering = Ordering::Less;
mem::swap(&mut entry1, &mut entry2);
}
match pairs.entry((entry1, entry2)) {
hash_map::Entry::Vacant(e) => {
e.insert(ordering);
}
hash_map::Entry::Occupied(e) => {
if e.get() != &ordering {
Err(ConflictingPrecedenceOrderingError {
precedence_1: entry1.to_string(),
precedence_2: entry2.to_string(),
})?;
}
}
}
}
}
}
let precedence_names = grammar
.precedence_orderings
.iter()
.flat_map(|l| l.iter())
.filter_map(|p| {
if let PrecedenceEntry::Name(n) = p {
Some(n)
} else {
None
}
})
.collect::<HashSet<&String>>();
for variable in &grammar.variables {
validate(&variable.name, &variable.rule, &precedence_names)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::grammars::VariableType;
#[test]
fn test_validate_precedences_with_undeclared_precedence() {
let grammar = InputGrammar {
precedence_orderings: vec![
vec![
PrecedenceEntry::Name("a".to_string()),
PrecedenceEntry::Name("b".to_string()),
],
vec![
PrecedenceEntry::Name("b".to_string()),
PrecedenceEntry::Name("c".to_string()),
PrecedenceEntry::Name("d".to_string()),
],
],
variables: vec![
Variable {
name: "v1".to_string(),
kind: VariableType::Named,
rule: Rule::Seq(vec![
Rule::prec_left(Precedence::Name("b".to_string()), Rule::string("w")),
Rule::prec(Precedence::Name("c".to_string()), Rule::string("x")),
]),
},
Variable {
name: "v2".to_string(),
kind: VariableType::Named,
rule: Rule::repeat(Rule::Choice(vec![
Rule::prec_left(Precedence::Name("omg".to_string()), Rule::string("y")),
Rule::prec(Precedence::Name("c".to_string()), Rule::string("z")),
])),
},
],
..Default::default()
};
let result = validate_precedences(&grammar);
assert_eq!(
result.unwrap_err().to_string(),
"Undeclared precedence 'omg' in rule 'v2'",
);
}
#[test]
fn test_validate_precedences_with_conflicting_order() {
let grammar = InputGrammar {
precedence_orderings: vec![
vec![
PrecedenceEntry::Name("a".to_string()),
PrecedenceEntry::Name("b".to_string()),
],
vec![
PrecedenceEntry::Name("b".to_string()),
PrecedenceEntry::Name("c".to_string()),
PrecedenceEntry::Name("a".to_string()),
],
],
variables: vec![
Variable {
name: "v1".to_string(),
kind: VariableType::Named,
rule: Rule::Seq(vec![
Rule::prec_left(Precedence::Name("b".to_string()), Rule::string("w")),
Rule::prec(Precedence::Name("c".to_string()), Rule::string("x")),
]),
},
Variable {
name: "v2".to_string(),
kind: VariableType::Named,
rule: Rule::repeat(Rule::Choice(vec![
Rule::prec_left(Precedence::Name("a".to_string()), Rule::string("y")),
Rule::prec(Precedence::Name("c".to_string()), Rule::string("z")),
])),
},
],
..Default::default()
};
let result = validate_precedences(&grammar);
assert_eq!(
result.unwrap_err().to_string(),
"Conflicting orderings for precedences 'a' and 'b'",
);
}
}

View file

@ -1,558 +0,0 @@
use std::collections::HashMap;
use anyhow::Result;
use serde::Serialize;
use thiserror::Error;
use crate::{
grammars::{InlinedProductionMap, LexicalGrammar, Production, ProductionStep, SyntaxGrammar},
rules::SymbolType,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
struct ProductionStepId {
// A `None` value here means that the production itself was produced via inlining,
// and is stored in the builder's `productions` vector, as opposed to being
// stored in one of the grammar's variables.
variable_index: Option<usize>,
production_index: usize,
step_index: usize,
}
struct InlinedProductionMapBuilder {
production_indices_by_step_id: HashMap<ProductionStepId, Vec<usize>>,
productions: Vec<Production>,
}
impl InlinedProductionMapBuilder {
fn build(mut self, grammar: &SyntaxGrammar) -> InlinedProductionMap {
let mut step_ids_to_process = Vec::new();
for (variable_index, variable) in grammar.variables.iter().enumerate() {
for production_index in 0..variable.productions.len() {
step_ids_to_process.push(ProductionStepId {
variable_index: Some(variable_index),
production_index,
step_index: 0,
});
while !step_ids_to_process.is_empty() {
let mut i = 0;
while i < step_ids_to_process.len() {
let step_id = step_ids_to_process[i];
if let Some(step) = self.production_step_for_id(step_id, grammar) {
if grammar.variables_to_inline.contains(&step.symbol) {
let inlined_step_ids = self
.inline_production_at_step(step_id, grammar)
.iter()
.copied()
.map(|production_index| ProductionStepId {
variable_index: None,
production_index,
step_index: step_id.step_index,
});
step_ids_to_process.splice(i..=i, inlined_step_ids);
} else {
step_ids_to_process[i] = ProductionStepId {
variable_index: step_id.variable_index,
production_index: step_id.production_index,
step_index: step_id.step_index + 1,
};
i += 1;
}
} else {
step_ids_to_process.remove(i);
}
}
}
}
}
let productions = self.productions;
let production_indices_by_step_id = self.production_indices_by_step_id;
let production_map = production_indices_by_step_id
.into_iter()
.map(|(step_id, production_indices)| {
let production = step_id.variable_index.map_or_else(
|| &productions[step_id.production_index],
|variable_index| {
&grammar.variables[variable_index].productions[step_id.production_index]
},
) as *const Production;
((production, step_id.step_index as u32), production_indices)
})
.collect();
InlinedProductionMap {
productions,
production_map,
}
}
fn inline_production_at_step<'a>(
&'a mut self,
step_id: ProductionStepId,
grammar: &'a SyntaxGrammar,
) -> &'a [usize] {
// Build a list of productions produced by inlining rules.
let mut i = 0;
let step_index = step_id.step_index;
let mut productions_to_add = vec![self.production_for_id(step_id, grammar).clone()];
while i < productions_to_add.len() {
if let Some(step) = productions_to_add[i].steps.get(step_index) {
let symbol = step.symbol;
if grammar.variables_to_inline.contains(&symbol) {
// Remove the production from the vector, replacing it with a placeholder.
let production = productions_to_add
.splice(i..=i, std::iter::once(&Production::default()).cloned())
.next()
.unwrap();
// Replace the placeholder with the inlined productions.
productions_to_add.splice(
i..=i,
grammar.variables[symbol.index].productions.iter().map(|p| {
let mut production = production.clone();
let removed_step = production
.steps
.splice(step_index..=step_index, p.steps.iter().cloned())
.next()
.unwrap();
let inserted_steps =
&mut production.steps[step_index..(step_index + p.steps.len())];
if let Some(alias) = removed_step.alias {
for inserted_step in inserted_steps.iter_mut() {
inserted_step.alias = Some(alias.clone());
}
}
if let Some(field_name) = removed_step.field_name {
for inserted_step in inserted_steps.iter_mut() {
inserted_step.field_name = Some(field_name.clone());
}
}
if let Some(last_inserted_step) = inserted_steps.last_mut() {
if last_inserted_step.precedence.is_none() {
last_inserted_step.precedence = removed_step.precedence;
}
if last_inserted_step.associativity.is_none() {
last_inserted_step.associativity = removed_step.associativity;
}
}
if p.dynamic_precedence.abs() > production.dynamic_precedence.abs() {
production.dynamic_precedence = p.dynamic_precedence;
}
production
}),
);
continue;
}
}
i += 1;
}
// Store all the computed productions.
let result = productions_to_add
.into_iter()
.map(|production| {
self.productions
.iter()
.position(|p| *p == production)
.unwrap_or_else(|| {
self.productions.push(production);
self.productions.len() - 1
})
})
.collect();
// Cache these productions based on the original production step.
self.production_indices_by_step_id
.entry(step_id)
.or_insert(result)
}
fn production_for_id<'a>(
&'a self,
id: ProductionStepId,
grammar: &'a SyntaxGrammar,
) -> &'a Production {
id.variable_index.map_or_else(
|| &self.productions[id.production_index],
|variable_index| &grammar.variables[variable_index].productions[id.production_index],
)
}
fn production_step_for_id<'a>(
&'a self,
id: ProductionStepId,
grammar: &'a SyntaxGrammar,
) -> Option<&'a ProductionStep> {
self.production_for_id(id, grammar).steps.get(id.step_index)
}
}
pub type ProcessInlinesResult<T> = Result<T, ProcessInlinesError>;
#[derive(Debug, Error, Serialize)]
pub enum ProcessInlinesError {
#[error("External token `{0}` cannot be inlined")]
ExternalToken(String),
#[error("Token `{0}` cannot be inlined")]
Token(String),
#[error("Rule `{0}` cannot be inlined because it is the first rule")]
FirstRule(String),
}
pub(super) fn process_inlines(
grammar: &SyntaxGrammar,
lexical_grammar: &LexicalGrammar,
) -> ProcessInlinesResult<InlinedProductionMap> {
for symbol in &grammar.variables_to_inline {
match symbol.kind {
SymbolType::External => {
Err(ProcessInlinesError::ExternalToken(
grammar.external_tokens[symbol.index].name.clone(),
))?;
}
SymbolType::Terminal => {
Err(ProcessInlinesError::Token(
lexical_grammar.variables[symbol.index].name.clone(),
))?;
}
SymbolType::NonTerminal if symbol.index == 0 => {
Err(ProcessInlinesError::FirstRule(
grammar.variables[symbol.index].name.clone(),
))?;
}
_ => {}
}
}
Ok(InlinedProductionMapBuilder {
productions: Vec::new(),
production_indices_by_step_id: HashMap::new(),
}
.build(grammar))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
grammars::{LexicalVariable, SyntaxVariable, VariableType},
rules::{Associativity, Precedence, Symbol},
};
#[test]
fn test_basic_inlining() {
let grammar = SyntaxGrammar {
variables_to_inline: vec![Symbol::non_terminal(1)],
variables: vec![
SyntaxVariable {
name: "non-terminal-0".to_string(),
kind: VariableType::Named,
productions: vec![Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(10)),
ProductionStep::new(Symbol::non_terminal(1)), // inlined
ProductionStep::new(Symbol::terminal(11)),
],
}],
},
SyntaxVariable {
name: "non-terminal-1".to_string(),
kind: VariableType::Named,
productions: vec![
Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(12)),
ProductionStep::new(Symbol::terminal(13)),
],
},
Production {
dynamic_precedence: -2,
steps: vec![ProductionStep::new(Symbol::terminal(14))],
},
],
},
],
..Default::default()
};
let inline_map = process_inlines(&grammar, &LexicalGrammar::default()).unwrap();
// Nothing to inline at step 0.
assert!(inline_map
.inlined_productions(&grammar.variables[0].productions[0], 0)
.is_none());
// Inlining variable 1 yields two productions.
assert_eq!(
inline_map
.inlined_productions(&grammar.variables[0].productions[0], 1)
.unwrap()
.cloned()
.collect::<Vec<_>>(),
vec![
Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(10)),
ProductionStep::new(Symbol::terminal(12)),
ProductionStep::new(Symbol::terminal(13)),
ProductionStep::new(Symbol::terminal(11)),
],
},
Production {
dynamic_precedence: -2,
steps: vec![
ProductionStep::new(Symbol::terminal(10)),
ProductionStep::new(Symbol::terminal(14)),
ProductionStep::new(Symbol::terminal(11)),
],
},
]
);
}
#[test]
fn test_nested_inlining() {
let grammar = SyntaxGrammar {
variables: vec![
SyntaxVariable {
name: "non-terminal-0".to_string(),
kind: VariableType::Named,
productions: vec![Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(10)),
ProductionStep::new(Symbol::non_terminal(1)), // inlined
ProductionStep::new(Symbol::terminal(11)),
ProductionStep::new(Symbol::non_terminal(2)), // inlined
ProductionStep::new(Symbol::terminal(12)),
],
}],
},
SyntaxVariable {
name: "non-terminal-1".to_string(),
kind: VariableType::Named,
productions: vec![
Production {
dynamic_precedence: 0,
steps: vec![ProductionStep::new(Symbol::terminal(13))],
},
Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::non_terminal(3)), // inlined
ProductionStep::new(Symbol::terminal(14)),
],
},
],
},
SyntaxVariable {
name: "non-terminal-2".to_string(),
kind: VariableType::Named,
productions: vec![Production {
dynamic_precedence: 0,
steps: vec![ProductionStep::new(Symbol::terminal(15))],
}],
},
SyntaxVariable {
name: "non-terminal-3".to_string(),
kind: VariableType::Named,
productions: vec![Production {
dynamic_precedence: 0,
steps: vec![ProductionStep::new(Symbol::terminal(16))],
}],
},
],
variables_to_inline: vec![
Symbol::non_terminal(1),
Symbol::non_terminal(2),
Symbol::non_terminal(3),
],
..Default::default()
};
let inline_map = process_inlines(&grammar, &LexicalGrammar::default()).unwrap();
let productions = inline_map
.inlined_productions(&grammar.variables[0].productions[0], 1)
.unwrap()
.collect::<Vec<_>>();
assert_eq!(
productions.iter().copied().cloned().collect::<Vec<_>>(),
vec![
Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(10)),
ProductionStep::new(Symbol::terminal(13)),
ProductionStep::new(Symbol::terminal(11)),
ProductionStep::new(Symbol::non_terminal(2)),
ProductionStep::new(Symbol::terminal(12)),
],
},
Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(10)),
ProductionStep::new(Symbol::terminal(16)),
ProductionStep::new(Symbol::terminal(14)),
ProductionStep::new(Symbol::terminal(11)),
ProductionStep::new(Symbol::non_terminal(2)),
ProductionStep::new(Symbol::terminal(12)),
],
},
]
);
assert_eq!(
inline_map
.inlined_productions(productions[0], 3)
.unwrap()
.cloned()
.collect::<Vec<_>>(),
vec![Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(10)),
ProductionStep::new(Symbol::terminal(13)),
ProductionStep::new(Symbol::terminal(11)),
ProductionStep::new(Symbol::terminal(15)),
ProductionStep::new(Symbol::terminal(12)),
],
},]
);
}
#[test]
fn test_inlining_with_precedence_and_alias() {
let grammar = SyntaxGrammar {
variables_to_inline: vec![Symbol::non_terminal(1), Symbol::non_terminal(2)],
variables: vec![
SyntaxVariable {
name: "non-terminal-0".to_string(),
kind: VariableType::Named,
productions: vec![Production {
dynamic_precedence: 0,
steps: vec![
// inlined
ProductionStep::new(Symbol::non_terminal(1))
.with_prec(Precedence::Integer(1), Some(Associativity::Left)),
ProductionStep::new(Symbol::terminal(10)),
// inlined
ProductionStep::new(Symbol::non_terminal(2))
.with_alias("outer_alias", true),
],
}],
},
SyntaxVariable {
name: "non-terminal-1".to_string(),
kind: VariableType::Named,
productions: vec![Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(11))
.with_prec(Precedence::Integer(2), None)
.with_alias("inner_alias", true),
ProductionStep::new(Symbol::terminal(12)),
],
}],
},
SyntaxVariable {
name: "non-terminal-2".to_string(),
kind: VariableType::Named,
productions: vec![Production {
dynamic_precedence: 0,
steps: vec![ProductionStep::new(Symbol::terminal(13))],
}],
},
],
..Default::default()
};
let inline_map = process_inlines(&grammar, &LexicalGrammar::default()).unwrap();
let productions = inline_map
.inlined_productions(&grammar.variables[0].productions[0], 0)
.unwrap()
.collect::<Vec<_>>();
assert_eq!(
productions.iter().copied().cloned().collect::<Vec<_>>(),
vec![Production {
dynamic_precedence: 0,
steps: vec![
// The first step in the inlined production retains its precedence
// and alias.
ProductionStep::new(Symbol::terminal(11))
.with_prec(Precedence::Integer(2), None)
.with_alias("inner_alias", true),
// The final step of the inlined production inherits the precedence of
// the inlined step.
ProductionStep::new(Symbol::terminal(12))
.with_prec(Precedence::Integer(1), Some(Associativity::Left)),
ProductionStep::new(Symbol::terminal(10)),
ProductionStep::new(Symbol::non_terminal(2)).with_alias("outer_alias", true),
]
}],
);
assert_eq!(
inline_map
.inlined_productions(productions[0], 3)
.unwrap()
.cloned()
.collect::<Vec<_>>(),
vec![Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(11))
.with_prec(Precedence::Integer(2), None)
.with_alias("inner_alias", true),
ProductionStep::new(Symbol::terminal(12))
.with_prec(Precedence::Integer(1), Some(Associativity::Left)),
ProductionStep::new(Symbol::terminal(10)),
// All steps of the inlined production inherit their alias from the
// inlined step.
ProductionStep::new(Symbol::terminal(13)).with_alias("outer_alias", true),
]
}],
);
}
#[test]
fn test_error_when_inlining_tokens() {
let lexical_grammar = LexicalGrammar {
variables: vec![LexicalVariable {
name: "something".to_string(),
kind: VariableType::Named,
implicit_precedence: 0,
start_state: 0,
}],
..Default::default()
};
let grammar = SyntaxGrammar {
variables_to_inline: vec![Symbol::terminal(0)],
variables: vec![SyntaxVariable {
name: "non-terminal-0".to_string(),
kind: VariableType::Named,
productions: vec![Production {
dynamic_precedence: 0,
steps: vec![ProductionStep::new(Symbol::terminal(0))],
}],
}],
..Default::default()
};
if let Err(error) = process_inlines(&grammar, &lexical_grammar) {
assert_eq!(error.to_string(), "Token `something` cannot be inlined");
} else {
panic!("expected an error, but got none");
}
}
}

View file

@ -1,544 +0,0 @@
use std::{collections::HashMap, fmt};
use serde::Serialize;
use smallbitvec::SmallBitVec;
use super::grammars::VariableType;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
pub enum SymbolType {
External,
End,
EndOfNonTerminalExtra,
Terminal,
NonTerminal,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
pub enum Associativity {
Left,
Right,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
pub struct Alias {
pub value: String,
pub is_named: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize)]
pub enum Precedence {
#[default]
None,
Integer(i32),
Name(String),
}
pub type AliasMap = HashMap<Symbol, Alias>;
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
pub struct MetadataParams {
pub precedence: Precedence,
pub dynamic_precedence: i32,
pub associativity: Option<Associativity>,
pub is_token: bool,
pub is_main_token: bool,
pub alias: Option<Alias>,
pub field_name: Option<String>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
pub struct Symbol {
pub kind: SymbolType,
pub index: usize,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
pub enum Rule {
Blank,
String(String),
Pattern(String, String),
NamedSymbol(String),
Symbol(Symbol),
Choice(Vec<Rule>),
Metadata {
params: MetadataParams,
rule: Box<Rule>,
},
Repeat(Box<Rule>),
Seq(Vec<Rule>),
Reserved {
rule: Box<Rule>,
context_name: String,
},
}
// Because tokens are represented as small (~400 max) unsigned integers,
// sets of tokens can be efficiently represented as bit vectors with each
// index corresponding to a token, and each value representing whether or not
// the token is present in the set.
#[derive(Default, Clone, PartialEq, Eq, Hash)]
pub struct TokenSet {
terminal_bits: SmallBitVec,
external_bits: SmallBitVec,
eof: bool,
end_of_nonterminal_extra: bool,
}
impl fmt::Debug for TokenSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
impl PartialOrd for TokenSet {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TokenSet {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.terminal_bits
.iter()
.cmp(other.terminal_bits.iter())
.then_with(|| self.external_bits.iter().cmp(other.external_bits.iter()))
.then_with(|| self.eof.cmp(&other.eof))
.then_with(|| {
self.end_of_nonterminal_extra
.cmp(&other.end_of_nonterminal_extra)
})
}
}
impl Rule {
pub fn field(name: String, content: Self) -> Self {
add_metadata(content, move |params| {
params.field_name = Some(name);
})
}
pub fn alias(content: Self, value: String, is_named: bool) -> Self {
add_metadata(content, move |params| {
params.alias = Some(Alias { value, is_named });
})
}
pub fn token(content: Self) -> Self {
add_metadata(content, |params| {
params.is_token = true;
})
}
pub fn immediate_token(content: Self) -> Self {
add_metadata(content, |params| {
params.is_token = true;
params.is_main_token = true;
})
}
pub fn prec(value: Precedence, content: Self) -> Self {
add_metadata(content, |params| {
params.precedence = value;
})
}
pub fn prec_left(value: Precedence, content: Self) -> Self {
add_metadata(content, |params| {
params.associativity = Some(Associativity::Left);
params.precedence = value;
})
}
pub fn prec_right(value: Precedence, content: Self) -> Self {
add_metadata(content, |params| {
params.associativity = Some(Associativity::Right);
params.precedence = value;
})
}
pub fn prec_dynamic(value: i32, content: Self) -> Self {
add_metadata(content, |params| {
params.dynamic_precedence = value;
})
}
pub fn repeat(rule: Self) -> Self {
Self::Repeat(Box::new(rule))
}
pub fn choice(rules: Vec<Self>) -> Self {
let mut elements = Vec::with_capacity(rules.len());
for rule in rules {
choice_helper(&mut elements, rule);
}
Self::Choice(elements)
}
pub const fn seq(rules: Vec<Self>) -> Self {
Self::Seq(rules)
}
pub fn is_empty(&self) -> bool {
match self {
Self::Blank | Self::Pattern(..) | Self::NamedSymbol(_) | Self::Symbol(_) => false,
Self::String(string) => string.is_empty(),
Self::Metadata { rule, .. } | Self::Repeat(rule) | Self::Reserved { rule, .. } => {
rule.is_empty()
}
Self::Choice(rules) => rules.iter().any(Self::is_empty),
Self::Seq(rules) => rules.iter().all(Self::is_empty),
}
}
}
impl Alias {
#[must_use]
pub const fn kind(&self) -> VariableType {
if self.is_named {
VariableType::Named
} else {
VariableType::Anonymous
}
}
}
impl Precedence {
#[must_use]
pub const fn is_none(&self) -> bool {
matches!(self, Self::None)
}
}
#[cfg(test)]
impl Rule {
#[must_use]
pub const fn terminal(index: usize) -> Self {
Self::Symbol(Symbol::terminal(index))
}
#[must_use]
pub const fn non_terminal(index: usize) -> Self {
Self::Symbol(Symbol::non_terminal(index))
}
#[must_use]
pub const fn external(index: usize) -> Self {
Self::Symbol(Symbol::external(index))
}
#[must_use]
pub fn named(name: &'static str) -> Self {
Self::NamedSymbol(name.to_string())
}
#[must_use]
pub fn string(value: &'static str) -> Self {
Self::String(value.to_string())
}
#[must_use]
pub fn pattern(value: &'static str, flags: &'static str) -> Self {
Self::Pattern(value.to_string(), flags.to_string())
}
}
impl Symbol {
#[must_use]
pub fn is_terminal(&self) -> bool {
self.kind == SymbolType::Terminal
}
#[must_use]
pub fn is_non_terminal(&self) -> bool {
self.kind == SymbolType::NonTerminal
}
#[must_use]
pub fn is_external(&self) -> bool {
self.kind == SymbolType::External
}
#[must_use]
pub fn is_eof(&self) -> bool {
self.kind == SymbolType::End
}
#[must_use]
pub const fn non_terminal(index: usize) -> Self {
Self {
kind: SymbolType::NonTerminal,
index,
}
}
#[must_use]
pub const fn terminal(index: usize) -> Self {
Self {
kind: SymbolType::Terminal,
index,
}
}
#[must_use]
pub const fn external(index: usize) -> Self {
Self {
kind: SymbolType::External,
index,
}
}
#[must_use]
pub const fn end() -> Self {
Self {
kind: SymbolType::End,
index: 0,
}
}
#[must_use]
pub const fn end_of_nonterminal_extra() -> Self {
Self {
kind: SymbolType::EndOfNonTerminalExtra,
index: 0,
}
}
}
impl From<Symbol> for Rule {
#[must_use]
fn from(symbol: Symbol) -> Self {
Self::Symbol(symbol)
}
}
impl TokenSet {
#[must_use]
pub const fn new() -> Self {
Self {
terminal_bits: SmallBitVec::new(),
external_bits: SmallBitVec::new(),
eof: false,
end_of_nonterminal_extra: false,
}
}
pub fn iter(&self) -> impl Iterator<Item = Symbol> + '_ {
self.terminal_bits
.iter()
.enumerate()
.filter_map(|(i, value)| {
if value {
Some(Symbol::terminal(i))
} else {
None
}
})
.chain(
self.external_bits
.iter()
.enumerate()
.filter_map(|(i, value)| {
if value {
Some(Symbol::external(i))
} else {
None
}
}),
)
.chain(if self.eof { Some(Symbol::end()) } else { None })
.chain(if self.end_of_nonterminal_extra {
Some(Symbol::end_of_nonterminal_extra())
} else {
None
})
}
pub fn terminals(&self) -> impl Iterator<Item = Symbol> + '_ {
self.terminal_bits
.iter()
.enumerate()
.filter_map(|(i, value)| {
if value {
Some(Symbol::terminal(i))
} else {
None
}
})
}
pub fn contains(&self, symbol: &Symbol) -> bool {
match symbol.kind {
SymbolType::NonTerminal => panic!("Cannot store non-terminals in a TokenSet"),
SymbolType::Terminal => self.terminal_bits.get(symbol.index).unwrap_or(false),
SymbolType::External => self.external_bits.get(symbol.index).unwrap_or(false),
SymbolType::End => self.eof,
SymbolType::EndOfNonTerminalExtra => self.end_of_nonterminal_extra,
}
}
pub fn contains_terminal(&self, index: usize) -> bool {
self.terminal_bits.get(index).unwrap_or(false)
}
pub fn insert(&mut self, other: Symbol) {
let vec = match other.kind {
SymbolType::NonTerminal => panic!("Cannot store non-terminals in a TokenSet"),
SymbolType::Terminal => &mut self.terminal_bits,
SymbolType::External => &mut self.external_bits,
SymbolType::End => {
self.eof = true;
return;
}
SymbolType::EndOfNonTerminalExtra => {
self.end_of_nonterminal_extra = true;
return;
}
};
if other.index >= vec.len() {
vec.resize(other.index + 1, false);
}
vec.set(other.index, true);
}
pub fn remove(&mut self, other: &Symbol) -> bool {
let vec = match other.kind {
SymbolType::NonTerminal => panic!("Cannot store non-terminals in a TokenSet"),
SymbolType::Terminal => &mut self.terminal_bits,
SymbolType::External => &mut self.external_bits,
SymbolType::End => {
return if self.eof {
self.eof = false;
true
} else {
false
}
}
SymbolType::EndOfNonTerminalExtra => {
return if self.end_of_nonterminal_extra {
self.end_of_nonterminal_extra = false;
true
} else {
false
};
}
};
if other.index < vec.len() && vec[other.index] {
vec.set(other.index, false);
while vec.last() == Some(false) {
vec.pop();
}
return true;
}
false
}
pub fn is_empty(&self) -> bool {
!self.eof
&& !self.end_of_nonterminal_extra
&& !self.terminal_bits.iter().any(|a| a)
&& !self.external_bits.iter().any(|a| a)
}
pub fn len(&self) -> usize {
self.eof as usize
+ self.end_of_nonterminal_extra as usize
+ self.terminal_bits.iter().filter(|b| *b).count()
+ self.external_bits.iter().filter(|b| *b).count()
}
pub fn insert_all_terminals(&mut self, other: &Self) -> bool {
let mut result = false;
if other.terminal_bits.len() > self.terminal_bits.len() {
self.terminal_bits.resize(other.terminal_bits.len(), false);
}
for (i, element) in other.terminal_bits.iter().enumerate() {
if element {
result |= !self.terminal_bits[i];
self.terminal_bits.set(i, element);
}
}
result
}
fn insert_all_externals(&mut self, other: &Self) -> bool {
let mut result = false;
if other.external_bits.len() > self.external_bits.len() {
self.external_bits.resize(other.external_bits.len(), false);
}
for (i, element) in other.external_bits.iter().enumerate() {
if element {
result |= !self.external_bits[i];
self.external_bits.set(i, element);
}
}
result
}
pub fn insert_all(&mut self, other: &Self) -> bool {
let mut result = false;
if other.eof {
result |= !self.eof;
self.eof = true;
}
if other.end_of_nonterminal_extra {
result |= !self.end_of_nonterminal_extra;
self.end_of_nonterminal_extra = true;
}
result |= self.insert_all_terminals(other);
result |= self.insert_all_externals(other);
result
}
}
impl FromIterator<Symbol> for TokenSet {
fn from_iter<T: IntoIterator<Item = Symbol>>(iter: T) -> Self {
let mut result = Self::new();
for symbol in iter {
result.insert(symbol);
}
result
}
}
fn add_metadata<T: FnOnce(&mut MetadataParams)>(input: Rule, f: T) -> Rule {
match input {
Rule::Metadata { rule, mut params } if !params.is_token => {
f(&mut params);
Rule::Metadata { rule, params }
}
_ => {
let mut params = MetadataParams::default();
f(&mut params);
Rule::Metadata {
rule: Box::new(input),
params,
}
}
}
}
fn choice_helper(result: &mut Vec<Rule>, rule: Rule) {
match rule {
Rule::Choice(elements) => {
for element in elements {
choice_helper(result, element);
}
}
_ => {
if !result.contains(&rule) {
result.push(rule);
}
}
}
}
impl fmt::Display for Precedence {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Integer(i) => write!(f, "{i}"),
Self::Name(s) => write!(f, "'{s}'"),
Self::None => write!(f, "none"),
}
}
}

View file

@ -1,167 +0,0 @@
use std::collections::BTreeMap;
use super::{
nfa::CharacterSet,
rules::{Alias, Symbol, TokenSet},
};
pub type ProductionInfoId = usize;
pub type ParseStateId = usize;
pub type LexStateId = usize;
use std::hash::BuildHasherDefault;
use indexmap::IndexMap;
use rustc_hash::FxHasher;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ParseAction {
Accept,
Shift {
state: ParseStateId,
is_repetition: bool,
},
ShiftExtra,
Recover,
Reduce {
symbol: Symbol,
child_count: usize,
dynamic_precedence: i32,
production_id: ProductionInfoId,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GotoAction {
Goto(ParseStateId),
ShiftExtra,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ParseTableEntry {
pub actions: Vec<ParseAction>,
pub reusable: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ParseState {
pub id: ParseStateId,
pub terminal_entries: IndexMap<Symbol, ParseTableEntry, BuildHasherDefault<FxHasher>>,
pub nonterminal_entries: IndexMap<Symbol, GotoAction, BuildHasherDefault<FxHasher>>,
pub reserved_words: TokenSet,
pub lex_state_id: usize,
pub external_lex_state_id: usize,
pub core_id: usize,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct FieldLocation {
pub index: usize,
pub inherited: bool,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct ProductionInfo {
pub alias_sequence: Vec<Option<Alias>>,
pub field_map: BTreeMap<String, Vec<FieldLocation>>,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct ParseTable {
pub states: Vec<ParseState>,
pub symbols: Vec<Symbol>,
pub production_infos: Vec<ProductionInfo>,
pub max_aliased_production_length: usize,
pub external_lex_states: Vec<TokenSet>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct AdvanceAction {
pub state: LexStateId,
pub in_main_token: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct LexState {
pub accept_action: Option<Symbol>,
pub eof_action: Option<AdvanceAction>,
pub advance_actions: Vec<(CharacterSet, AdvanceAction)>,
}
#[derive(Debug, PartialEq, Eq, Default)]
pub struct LexTable {
pub states: Vec<LexState>,
}
impl ParseTableEntry {
#[must_use]
pub const fn new() -> Self {
Self {
reusable: true,
actions: Vec::new(),
}
}
}
impl ParseState {
pub fn is_end_of_non_terminal_extra(&self) -> bool {
self.terminal_entries
.contains_key(&Symbol::end_of_nonterminal_extra())
}
pub fn referenced_states(&self) -> impl Iterator<Item = ParseStateId> + '_ {
self.terminal_entries
.iter()
.flat_map(|(_, entry)| {
entry.actions.iter().filter_map(|action| match action {
ParseAction::Shift { state, .. } => Some(*state),
_ => None,
})
})
.chain(self.nonterminal_entries.iter().filter_map(|(_, action)| {
if let GotoAction::Goto(state) = action {
Some(*state)
} else {
None
}
}))
}
pub fn update_referenced_states<F>(&mut self, mut f: F)
where
F: FnMut(usize, &Self) -> usize,
{
let mut updates = Vec::new();
for (symbol, entry) in &self.terminal_entries {
for (i, action) in entry.actions.iter().enumerate() {
if let ParseAction::Shift { state, .. } = action {
let result = f(*state, self);
if result != *state {
updates.push((*symbol, i, result));
}
}
}
}
for (symbol, action) in &self.nonterminal_entries {
if let GotoAction::Goto(other_state) = action {
let result = f(*other_state, self);
if result != *other_state {
updates.push((*symbol, 0, result));
}
}
}
for (symbol, action_index, new_state) in updates {
if symbol.is_non_terminal() {
self.nonterminal_entries
.insert(symbol, GotoAction::Goto(new_state));
} else {
let entry = self.terminal_entries.get_mut(&symbol).unwrap();
if let ParseAction::Shift { is_repetition, .. } = entry.actions[action_index] {
entry.actions[action_index] = ParseAction::Shift {
state: new_state,
is_repetition,
};
}
}
}
}
}

View file

@ -1 +0,0 @@
4.0.1

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,30 +0,0 @@
use log::{LevelFilter, Log, Metadata, Record};
#[allow(dead_code)]
struct Logger {
pub filter: Option<String>,
}
impl Log for Logger {
fn enabled(&self, _: &Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
eprintln!(
"[{}] {}",
record
.module_path()
.unwrap_or_default()
.trim_start_matches("rust_tree_sitter_cli::"),
record.args()
);
}
fn flush(&self) {}
}
pub fn init() {
log::set_boxed_logger(Box::new(Logger { filter: None })).unwrap();
log::set_max_level(LevelFilter::Info);
}

View file

@ -1,410 +0,0 @@
<head>
<meta charset="utf-8">
<title>tree-sitter THE_LANGUAGE_NAME</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/clusterize.js/0.19.0/clusterize.min.css">
<link rel="icon" type="image/png" href="https://tree-sitter.github.io/tree-sitter/assets/images/favicon-32x32.png"
sizes="32x32" />
<link rel="icon" type="image/png" href="https://tree-sitter.github.io/tree-sitter/assets/images/favicon-16x16.png"
sizes="16x16" />
</head>
<body>
<div id="playground-container" style="visibility: hidden;">
<header>
<div class="header-item">
<span class="language-name">Language: THE_LANGUAGE_NAME</span>
</div>
<div class="header-item">
<input id="logging-checkbox" type="checkbox">
<label for="logging-checkbox">log</label>
</div>
<div class="header-item">
<input id="anonymous-nodes-checkbox" type="checkbox">
<label for="anonymous-nodes-checkbox">show anonymous nodes</label>
</div>
<div class="header-item">
<input id="query-checkbox" type="checkbox">
<label for="query-checkbox">query</label>
</div>
<div class="header-item">
<input id="accessibility-checkbox" type="checkbox">
<label for="accessibility-checkbox">accessibility</label>
</div>
<div class="header-item">
<label for="update-time">parse time: </label>
<span id="update-time"></span>
</div>
<div class="header-item">
<a href="https://tree-sitter.github.io/tree-sitter/7-playground.html#about">(?)</a>
</div>
<select id="language-select" style="display: none;">
<option value="parser">Parser</option>
</select>
<div class="header-item">
<button id="theme-toggle" class="theme-toggle" aria-label="Toggle theme">
<svg class="sun-icon" viewBox="0 0 24 24" width="16" height="16">
<path fill="currentColor"
d="M12 17.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zm0 1.5a7 7 0 1 1 0-14 7 7 0 0 1 0 14zm0-16a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0V4a1 1 0 0 1 1-1zm0 15a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0v-2a1 1 0 0 1 1-1zm9-9a1 1 0 0 1-1 1h-2a1 1 0 1 1 0-2h2a1 1 0 0 1 1 1zM4 12a1 1 0 0 1-1 1H1a1 1 0 1 1 0-2h2a1 1 0 0 1 1 1z" />
</svg>
<svg class="moon-icon" viewBox="0 0 24 24" width="16" height="16">
<path fill="currentColor"
d="M12.1 22c-5.5 0-10-4.5-10-10s4.5-10 10-10c.2 0 .3 0 .5.1-1.3 1.4-2 3.2-2 5.2 0 4.1 3.4 7.5 7.5 7.5 2 0 3.8-.7 5.2-2 .1.2.1.3.1.5 0 5.4-4.5 9.7-10 9.7z" />
</svg>
</button>
</div>
</header>
<main>
<div id="input-pane">
<div class="panel-header">Code</div>
<div id="code-container">
<textarea id="code-input"></textarea>
</div>
<div id="query-container" style="visibility: hidden; position: absolute;">
<div class="panel-header">Query</div>
<textarea id="query-input"></textarea>
</div>
</div>
<div id="output-container-scroll">
<div class="panel-header">Tree</div>
<pre id="output-container" class="highlight"></pre>
</div>
</main>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/clusterize.js/0.19.0/clusterize.min.js"></script>
<script>LANGUAGE_BASE_URL = "";</script>
<script type="module" src="playground.js"></script>
<script type="module">
import * as TreeSitter from './tree-sitter.js';
window.TreeSitter = TreeSitter;
setTimeout(() => window.initializePlayground({local: true}), 1)
</script>
<style>
/* Base Variables */
:root {
--light-bg: #f9f9f9;
--light-border: #e0e0e0;
--light-text: #333;
--light-hover-border: #c1c1c1;
--light-scrollbar-track: #f1f1f1;
--light-scrollbar-thumb: #c1c1c1;
--light-scrollbar-thumb-hover: #a8a8a8;
--dark-bg: #1d1f21;
--dark-border: #2d2d2d;
--dark-text: #c5c8c6;
--dark-panel-bg: #252526;
--dark-code-bg: #1e1e1e;
--dark-scrollbar-track: #25282c;
--dark-scrollbar-thumb: #4a4d51;
--dark-scrollbar-thumb-hover: #5a5d61;
--primary-color: #0550ae;
--primary-color-alpha: rgba(5, 80, 174, 0.1);
--primary-color-alpha-dark: rgba(121, 192, 255, 0.1);
--selection-color: rgba(39, 95, 255, 0.3);
}
/* Theme Colors */
[data-theme="dark"] {
--bg-color: var(--dark-bg);
--border-color: var(--dark-border);
--text-color: var(--dark-text);
--panel-bg: var(--dark-panel-bg);
--code-bg: var(--dark-code-bg);
}
[data-theme="light"] {
--bg-color: var(--light-bg);
--border-color: var(--light-border);
--text-color: var(--light-text);
--panel-bg: white;
--code-bg: white;
}
/* Base Styles */
body {
margin: 0;
padding: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
}
/* Layout */
#playground-container {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
background-color: var(--bg-color);
}
header {
padding: 16px 24px;
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
gap: 20px;
background-color: var(--panel-bg);
font-size: 14px;
}
.header-item {
display: flex;
align-items: center;
gap: 8px;
}
.language-name {
font-weight: 600;
}
main {
flex: 1;
display: flex;
overflow: hidden;
}
#input-pane {
width: 50%;
display: flex;
flex-direction: column;
border-right: 1px solid var(--border-color);
background-color: var(--panel-bg);
overflow: hidden;
}
#code-container {
flex: 1;
min-height: 0;
position: relative;
border-bottom: 1px solid var(--border-color);
display: flex;
flex-direction: column;
}
#query-container:not([style*="visibility: hidden"]) {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
#query-container .panel-header {
flex: 0 0 auto;
}
#query-container .CodeMirror {
flex: 1;
position: relative;
min-height: 0;
}
#output-container-scroll {
width: 50%;
overflow: auto;
background-color: var(--panel-bg);
padding: 0;
display: flex;
flex-direction: column;
}
#output-container {
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
line-height: 1.5;
margin: 0;
padding: 16px;
}
.panel-header {
padding: 8px 16px;
font-weight: 600;
font-size: 14px;
border-bottom: 1px solid var(--border-color);
background-color: var(--panel-bg);
}
.CodeMirror {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
height: 100%;
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
font-size: 14px;
line-height: 1.6;
background-color: var(--code-bg) !important;
color: var(--text-color) !important;
}
.query-error {
text-decoration: underline red dashed;
-webkit-text-decoration: underline red dashed;
}
/* Scrollbars */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
border-radius: 4px;
background: var(--light-scrollbar-track);
}
::-webkit-scrollbar-thumb {
border-radius: 4px;
background: var(--light-scrollbar-thumb);
}
::-webkit-scrollbar-thumb:hover {
background: var(--light-scrollbar-thumb-hover);
}
[data-theme="dark"] {
::-webkit-scrollbar-track {
background: var(--dark-scrollbar-track) !important;
}
::-webkit-scrollbar-thumb {
background: var(--dark-scrollbar-thumb) !important;
}
::-webkit-scrollbar-thumb:hover {
background: var(--dark-scrollbar-thumb-hover) !important;
}
}
/* Theme Toggle */
.theme-toggle {
background: none;
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 6px;
cursor: pointer;
color: var(--text-color);
}
.theme-toggle:hover {
background-color: var(--primary-color-alpha);
}
[data-theme="light"] .moon-icon,
[data-theme="dark"] .sun-icon {
display: none;
}
/* Form Elements */
input[type="checkbox"] {
margin-right: 6px;
vertical-align: middle;
}
label {
font-size: 14px;
margin-right: 16px;
cursor: pointer;
}
#output-container a {
cursor: pointer;
text-decoration: none;
color: #040404;
padding: 2px;
}
#output-container a:hover {
text-decoration: underline;
}
#output-container a.node-link.named {
color: #0550ae;
}
#output-container a.node-link.anonymous {
color: #116329;
}
#output-container a.node-link.anonymous:before {
content: '"';
}
#output-container a.node-link.anonymous:after {
content: '"';
}
#output-container a.node-link.error {
color: #cf222e;
}
#output-container a.highlighted {
background-color: #d9d9d9;
color: red;
border-radius: 3px;
text-decoration: underline;
}
/* Dark Theme Node Colors */
[data-theme="dark"] {
& #output-container a {
color: #d4d4d4;
}
& #output-container a.node-link.named {
color: #79c0ff;
}
& #output-container a.node-link.anonymous {
color: #7ee787;
}
& #output-container a.node-link.error {
color: #ff7b72;
}
& #output-container a.highlighted {
background-color: #373b41;
color: red;
}
& .CodeMirror {
background-color: var(--dark-code-bg) !important;
color: var(--dark-text) !important;
}
& .CodeMirror-gutters {
background-color: var(--dark-panel-bg) !important;
border-color: var(--dark-border) !important;
}
& .CodeMirror-cursor {
border-color: var(--dark-text) !important;
}
& .CodeMirror-selected {
background-color: rgba(255, 255, 255, 0.1) !important;
}
}
</style>
</body>

View file

@ -1,148 +0,0 @@
use std::{
fs,
io::{self, Write},
ops::Range,
path::Path,
time::Instant,
};
use anstyle::AnsiColor;
use anyhow::{Context, Result};
use streaming_iterator::StreamingIterator;
use tree_sitter::{Language, Parser, Point, Query, QueryCursor};
use crate::{
query_testing::{self, to_utf8_point},
test::paint,
};
#[allow(clippy::too_many_arguments)]
pub fn query_file_at_path(
language: &Language,
path: &Path,
name: &str,
query_path: &Path,
ordered_captures: bool,
byte_range: Option<Range<usize>>,
point_range: Option<Range<Point>>,
should_test: bool,
quiet: bool,
print_time: bool,
stdin: bool,
) -> Result<()> {
let stdout = io::stdout();
let mut stdout = stdout.lock();
let query_source = fs::read_to_string(query_path)
.with_context(|| format!("Error reading query file {query_path:?}"))?;
let query = Query::new(language, &query_source).with_context(|| "Query compilation failed")?;
let mut query_cursor = QueryCursor::new();
if let Some(range) = byte_range {
query_cursor.set_byte_range(range);
}
if let Some(range) = point_range {
query_cursor.set_point_range(range);
}
let mut parser = Parser::new();
parser.set_language(language)?;
let mut results = Vec::new();
if !should_test && !stdin {
writeln!(&mut stdout, "{name}")?;
}
let source_code =
fs::read(path).with_context(|| format!("Error reading source file {path:?}"))?;
let tree = parser.parse(&source_code, None).unwrap();
let start = Instant::now();
if ordered_captures {
let mut captures = query_cursor.captures(&query, tree.root_node(), source_code.as_slice());
while let Some((mat, capture_index)) = captures.next() {
let capture = mat.captures[*capture_index];
let capture_name = &query.capture_names()[capture.index as usize];
if !quiet && !should_test {
writeln!(
&mut stdout,
" pattern: {:>2}, capture: {} - {capture_name}, start: {}, end: {}, text: `{}`",
mat.pattern_index,
capture.index,
capture.node.start_position(),
capture.node.end_position(),
capture.node.utf8_text(&source_code).unwrap_or("")
)?;
}
results.push(query_testing::CaptureInfo {
name: (*capture_name).to_string(),
start: to_utf8_point(capture.node.start_position(), source_code.as_slice()),
end: to_utf8_point(capture.node.end_position(), source_code.as_slice()),
});
}
} else {
let mut matches = query_cursor.matches(&query, tree.root_node(), source_code.as_slice());
while let Some(m) = matches.next() {
if !quiet && !should_test {
writeln!(&mut stdout, " pattern: {}", m.pattern_index)?;
}
for capture in m.captures {
let start = capture.node.start_position();
let end = capture.node.end_position();
let capture_name = &query.capture_names()[capture.index as usize];
if !quiet && !should_test {
if end.row == start.row {
writeln!(
&mut stdout,
" capture: {} - {capture_name}, start: {start}, end: {end}, text: `{}`",
capture.index,
capture.node.utf8_text(&source_code).unwrap_or("")
)?;
} else {
writeln!(
&mut stdout,
" capture: {capture_name}, start: {start}, end: {end}",
)?;
}
}
results.push(query_testing::CaptureInfo {
name: (*capture_name).to_string(),
start: to_utf8_point(capture.node.start_position(), source_code.as_slice()),
end: to_utf8_point(capture.node.end_position(), source_code.as_slice()),
});
}
}
}
if query_cursor.did_exceed_match_limit() {
writeln!(
&mut stdout,
" WARNING: Query exceeded maximum number of in-progress captures!"
)?;
}
if should_test {
let path_name = if stdin {
"stdin"
} else {
Path::new(&path).file_name().unwrap().to_str().unwrap()
};
match query_testing::assert_expected_captures(&results, path, &mut parser, language) {
Ok(assertion_count) => {
println!(
" ✓ {} ({} assertions)",
paint(Some(AnsiColor::Green), path_name),
assertion_count
);
}
Err(e) => {
println!("{}", paint(Some(AnsiColor::Red), path_name));
return Err(e);
}
}
}
if print_time {
writeln!(&mut stdout, "{:?}", start.elapsed())?;
}
Ok(())
}

View file

@ -1,42 +0,0 @@
"""PARSER_DESCRIPTION"""
from importlib.resources import files as _files
from ._binding import language
def _get_query(name, file):
query = _files(f"{__package__}.queries") / file
globals()[name] = query.read_text()
return globals()[name]
def __getattr__(name):
# NOTE: uncomment these to include any queries that this grammar contains:
# if name == "HIGHLIGHTS_QUERY":
# return _get_query("HIGHLIGHTS_QUERY", "highlights.scm")
# if name == "INJECTIONS_QUERY":
# return _get_query("INJECTIONS_QUERY", "injections.scm")
# if name == "LOCALS_QUERY":
# return _get_query("LOCALS_QUERY", "locals.scm")
# if name == "TAGS_QUERY":
# return _get_query("TAGS_QUERY", "tags.scm")
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
__all__ = [
"language",
# "HIGHLIGHTS_QUERY",
# "INJECTIONS_QUERY",
# "LOCALS_QUERY",
# "TAGS_QUERY",
]
def __dir__():
return sorted(__all__ + [
"__all__", "__builtins__", "__cached__", "__doc__", "__file__",
"__loader__", "__name__", "__package__", "__path__", "__spec__",
])

View file

@ -1,10 +0,0 @@
from typing import Final
# NOTE: uncomment these to include any queries that this grammar contains:
# HIGHLIGHTS_QUERY: Final[str]
# INJECTIONS_QUERY: Final[str]
# LOCALS_QUERY: Final[str]
# TAGS_QUERY: Final[str]
def language() -> object: ...

View file

@ -1,9 +0,0 @@
const assert = require("node:assert");
const { test } = require("node:test");
const Parser = require("tree-sitter");
test("can load grammar", () => {
const parser = new Parser();
assert.doesNotThrow(() => parser.setLanguage(require(".")));
});

View file

@ -1,21 +0,0 @@
fn main() {
let src_dir = std::path::Path::new("src");
let mut c_config = cc::Build::new();
c_config.std("c11").include(src_dir);
#[cfg(target_env = "msvc")]
c_config.flag("-utf-8");
let parser_path = src_dir.join("parser.c");
c_config.file(&parser_path);
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
let scanner_path = src_dir.join("scanner.c");
if scanner_path.exists() {
c_config.file(&scanner_path);
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
}
c_config.compile("tree-sitter-KEBAB_PARSER_NAME");
}

View file

@ -1,79 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const shared = b.option(bool, "build-shared", "Build a shared library") orelse true;
const reuse_alloc = b.option(bool, "reuse-allocator", "Reuse the library allocator") orelse false;
const lib: *std.Build.Step.Compile = if (shared) b.addSharedLibrary(.{
.name = "tree-sitter-PARSER_NAME",
.pic = true,
.target = target,
.optimize = optimize,
.link_libc = true,
}) else b.addStaticLibrary(.{
.name = "tree-sitter-PARSER_NAME",
.target = target,
.optimize = optimize,
.link_libc = true,
});
lib.addCSourceFile(.{
.file = b.path("src/parser.c"),
.flags = &.{"-std=c11"},
});
if (hasScanner(b.build_root.handle)) {
lib.addCSourceFile(.{
.file = b.path("src/scanner.c"),
.flags = &.{"-std=c11"},
});
}
if (reuse_alloc) {
lib.root_module.addCMacro("TREE_SITTER_REUSE_ALLOCATOR", "");
}
if (optimize == .Debug) {
lib.root_module.addCMacro("TREE_SITTER_DEBUG", "");
}
lib.addIncludePath(b.path("src"));
b.installArtifact(lib);
b.installFile("src/node-types.json", "node-types.json");
b.installDirectory(.{ .source_dir = b.path("queries"), .install_dir = .prefix, .install_subdir = "queries", .include_extensions = &.{"scm"} });
const module = b.addModule("tree-sitter-PARSER_NAME", .{
.root_source_file = b.path("bindings/zig/root.zig"),
.target = target,
.optimize = optimize,
});
module.linkLibrary(lib);
const ts_dep = b.dependency("tree-sitter", .{});
const ts_mod = ts_dep.module("tree-sitter");
module.addImport("tree-sitter", ts_mod);
//
// Tests
//
const tests = b.addTest(.{
.root_source_file = b.path("bindings/zig/root.zig"),
.target = target,
.optimize = optimize,
});
tests.linkLibrary(lib);
tests.root_module.addImport("tree-sitter", ts_mod);
const run_tests = b.addRunArtifact(tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_tests.step);
}
inline fn hasScanner(dir: std.fs.Dir) bool {
dir.access("src/scanner.c", .{}) catch return false;
return true;
}

View file

@ -1,17 +0,0 @@
.{
.name = "tree-sitter-PARSER_NAME",
.version = "PARSER_VERSION",
.dependencies = .{ .@"tree-sitter" = .{
.url = "https://github.com/tree-sitter/zig-tree-sitter/archive/refs/tags/v0.25.0.tar.gz",
.hash = "12201a8d5e840678bbbf5128e605519c4024af422295d68e2ba2090e675328e5811d",
} },
.paths = .{
"build.zig",
"build.zig.zon",
"bindings/zig",
"src",
"queries",
"LICENSE",
"README.md",
},
}

View file

@ -1,27 +0,0 @@
type BaseNode = {
type: string;
named: boolean;
};
type ChildNode = {
multiple: boolean;
required: boolean;
types: BaseNode[];
};
type NodeInfo =
| (BaseNode & {
subtypes: BaseNode[];
})
| (BaseNode & {
fields: { [name: string]: ChildNode };
children: ChildNode[];
});
type Language = {
language: unknown;
nodeTypeInfo: NodeInfo[];
};
declare const language: Language;
export = language;

View file

@ -1,11 +0,0 @@
const root = require("path").join(__dirname, "..", "..");
module.exports =
typeof process.versions.bun === "string"
// Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time
? require(`../../prebuilds/${process.platform}-${process.arch}/tree-sitter-KEBAB_PARSER_NAME.node`)
: require("node-gyp-build")(root);
try {
module.exports.nodeTypeInfo = require("../../src/node-types.json");
} catch (_) {}

View file

@ -1,53 +0,0 @@
//! This crate provides CAMEL_PARSER_NAME language support for the [tree-sitter][] parsing library.
//!
//! Typically, you will use the [LANGUAGE][] constant to add this language to a
//! tree-sitter [Parser][], and then use the parser to parse some code:
//!
//! ```
//! let code = r#"
//! "#;
//! let mut parser = tree_sitter::Parser::new();
//! let language = tree_sitter_PARSER_NAME::LANGUAGE;
//! parser
//! .set_language(&language.into())
//! .expect("Error loading TITLE_PARSER_NAME parser");
//! let tree = parser.parse(code, None).unwrap();
//! assert!(!tree.root_node().has_error());
//! ```
//!
//! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html
//! [tree-sitter]: https://tree-sitter.github.io/
use tree_sitter_language::LanguageFn;
extern "C" {
fn tree_sitter_PARSER_NAME() -> *const ();
}
/// The tree-sitter [`LanguageFn`][LanguageFn] for this grammar.
///
/// [LanguageFn]: https://docs.rs/tree-sitter-language/*/tree_sitter_language/struct.LanguageFn.html
pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_PARSER_NAME) };
/// The content of the [`node-types.json`][] file for this grammar.
///
/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers/6-static-node-types
pub const NODE_TYPES: &str = include_str!("../../src/node-types.json");
// NOTE: uncomment these to include any queries that this grammar contains:
// pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm");
// pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm");
// pub const LOCALS_QUERY: &str = include_str!("../../queries/locals.scm");
// pub const TAGS_QUERY: &str = include_str!("../../queries/tags.scm");
#[cfg(test)]
mod tests {
#[test]
fn test_can_load_grammar() {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(&super::LANGUAGE.into())
.expect("Error loading TITLE_PARSER_NAME parser");
}
}

View file

@ -1,19 +0,0 @@
const testing = @import("std").testing;
const ts = @import("tree-sitter");
const Language = ts.Language;
const Parser = ts.Parser;
pub extern fn tree_sitter_PARSER_NAME() callconv(.C) *const Language;
pub export fn language() *const Language {
return tree_sitter_PARSER_NAME();
}
test "can load grammar" {
const parser = Parser.create();
defer parser.destroy();
try testing.expectEqual(parser.setLanguage(language()), void{});
try testing.expectEqual(parser.getLanguage(), tree_sitter_PARSER_NAME());
}

File diff suppressed because it is too large Load diff

View file

@ -1,278 +0,0 @@
use std::{
future::Future,
pin::{pin, Pin},
ptr,
task::{self, Context, Poll, RawWaker, RawWakerVTable, Waker},
};
use tree_sitter::Parser;
use super::helpers::fixtures::get_language;
#[test]
fn test_node_in_fut() {
let (ret, pended) = tokio_like_spawn(async {
let mut parser = Parser::new();
let language = get_language("bash");
parser.set_language(&language).unwrap();
let tree = parser.parse("#", None).unwrap();
let root = tree.root_node();
let root_ref = &root;
let fut_val_fn = || async {
yield_now().await;
root.child(0).unwrap().kind()
};
yield_now().await;
let fut_ref_fn = || async {
yield_now().await;
root_ref.child(0).unwrap().kind()
};
let f1 = fut_val_fn().await;
let f2 = fut_ref_fn().await;
assert_eq!(f1, f2);
let fut_val = async {
yield_now().await;
root.child(0).unwrap().kind()
};
let fut_ref = async {
yield_now().await;
root_ref.child(0).unwrap().kind()
};
let f1 = fut_val.await;
let f2 = fut_ref.await;
assert_eq!(f1, f2);
f1
})
.join();
assert_eq!(ret, "comment");
assert_eq!(pended, 5);
}
#[test]
fn test_node_and_cursor_ref_in_fut() {
let ((), pended) = tokio_like_spawn(async {
let mut parser = Parser::new();
let language = get_language("c");
parser.set_language(&language).unwrap();
let tree = parser.parse("#", None).unwrap();
let root = tree.root_node();
let root_ref = &root;
let mut cursor = tree.walk();
let cursor_ref = &mut cursor;
cursor_ref.goto_first_child();
let fut_val = async {
yield_now().await;
let _ = root.to_sexp();
};
yield_now().await;
let fut_ref = async {
yield_now().await;
let _ = root_ref.to_sexp();
cursor_ref.goto_first_child();
};
fut_val.await;
fut_ref.await;
cursor_ref.goto_first_child();
})
.join();
assert_eq!(pended, 3);
}
#[test]
fn test_node_and_cursor_ref_in_fut_with_fut_fabrics() {
let ((), pended) = tokio_like_spawn(async {
let mut parser = Parser::new();
let language = get_language("javascript");
parser.set_language(&language).unwrap();
let tree = parser.parse("#", None).unwrap();
let root = tree.root_node();
let root_ref = &root;
let mut cursor = tree.walk();
let cursor_ref = &mut cursor;
cursor_ref.goto_first_child();
let fut_val = || async {
yield_now().await;
let _ = root.to_sexp();
};
yield_now().await;
let fut_ref = || async move {
yield_now().await;
let _ = root_ref.to_sexp();
cursor_ref.goto_first_child();
};
fut_val().await;
fut_val().await;
fut_ref().await;
})
.join();
assert_eq!(pended, 4);
}
#[test]
fn test_node_and_cursor_ref_in_fut_with_inner_spawns() {
let (ret, pended) = tokio_like_spawn(async {
let mut parser = Parser::new();
let language = get_language("rust");
parser.set_language(&language).unwrap();
let tree = parser.parse("#", None).unwrap();
let mut cursor = tree.walk();
let cursor_ref = &mut cursor;
cursor_ref.goto_first_child();
let fut_val = || {
let tree = tree.clone();
async move {
let root = tree.root_node();
let mut cursor = tree.walk();
let cursor_ref = &mut cursor;
yield_now().await;
let _ = root.to_sexp();
cursor_ref.goto_first_child();
}
};
yield_now().await;
let fut_ref = || {
let tree = tree.clone();
async move {
let root = tree.root_node();
let root_ref = &root;
let mut cursor = tree.walk();
let cursor_ref = &mut cursor;
yield_now().await;
let _ = root_ref.to_sexp();
cursor_ref.goto_first_child();
}
};
let ((), p1) = tokio_like_spawn(fut_val()).await.unwrap();
let ((), p2) = tokio_like_spawn(fut_ref()).await.unwrap();
cursor_ref.goto_first_child();
fut_val().await;
fut_val().await;
fut_ref().await;
cursor_ref.goto_first_child();
p1 + p2
})
.join();
assert_eq!(pended, 4);
assert_eq!(ret, 2);
}
fn tokio_like_spawn<T>(future: T) -> JoinHandle<(T::Output, usize)>
where
T: Future + Send + 'static,
T::Output: Send + 'static,
{
// No runtime, just noop waker
let waker = noop_waker();
let mut cx = task::Context::from_waker(&waker);
let mut pending = 0;
let mut future = pin!(future);
let ret = loop {
match future.as_mut().poll(&mut cx) {
Poll::Pending => pending += 1,
Poll::Ready(r) => {
break r;
}
}
};
JoinHandle::new((ret, pending))
}
async fn yield_now() {
struct SimpleYieldNow {
yielded: bool,
}
impl Future for SimpleYieldNow {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
cx.waker().wake_by_ref();
if self.yielded {
return Poll::Ready(());
}
self.yielded = true;
Poll::Pending
}
}
SimpleYieldNow { yielded: false }.await;
}
pub fn noop_waker() -> Waker {
const VTABLE: RawWakerVTable = RawWakerVTable::new(
// Cloning just returns a new no-op raw waker
|_| RAW,
// `wake` does nothing
|_| {},
// `wake_by_ref` does nothing
|_| {},
// Dropping does nothing as we don't allocate anything
|_| {},
);
const RAW: RawWaker = RawWaker::new(ptr::null(), &VTABLE);
unsafe { Waker::from_raw(RAW) }
}
struct JoinHandle<T> {
data: Option<T>,
}
impl<T> JoinHandle<T> {
#[must_use]
const fn new(data: T) -> Self {
Self { data: Some(data) }
}
fn join(&mut self) -> T {
self.data.take().unwrap()
}
}
impl<T: Unpin> Future for JoinHandle<T> {
type Output = std::result::Result<T, ()>;
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
let data = self.get_mut().data.take().unwrap();
Poll::Ready(Ok(data))
}
}

View file

@ -1,121 +0,0 @@
use std::{
collections::HashMap,
os::raw::c_void,
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
Mutex,
},
};
#[ctor::ctor]
unsafe fn initialize_allocation_recording() {
tree_sitter::set_allocator(
Some(ts_record_malloc),
Some(ts_record_calloc),
Some(ts_record_realloc),
Some(ts_record_free),
);
}
#[derive(Debug, PartialEq, Eq, Hash)]
struct Allocation(*const c_void);
unsafe impl Send for Allocation {}
unsafe impl Sync for Allocation {}
#[derive(Default)]
struct AllocationRecorder {
enabled: AtomicBool,
allocation_count: AtomicUsize,
outstanding_allocations: Mutex<HashMap<Allocation, usize>>,
}
thread_local! {
static RECORDER: AllocationRecorder = AllocationRecorder::default();
}
extern "C" {
fn malloc(size: usize) -> *mut c_void;
fn calloc(count: usize, size: usize) -> *mut c_void;
fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void;
fn free(ptr: *mut c_void);
}
pub fn record<T>(f: impl FnOnce() -> T) -> T {
RECORDER.with(|recorder| {
recorder.enabled.store(true, SeqCst);
recorder.allocation_count.store(0, SeqCst);
recorder.outstanding_allocations.lock().unwrap().clear();
});
let value = f();
let outstanding_allocation_indices = RECORDER.with(|recorder| {
recorder.enabled.store(false, SeqCst);
recorder.allocation_count.store(0, SeqCst);
recorder
.outstanding_allocations
.lock()
.unwrap()
.drain()
.map(|e| e.1)
.collect::<Vec<_>>()
});
assert!(
outstanding_allocation_indices.is_empty(),
"Leaked allocation indices: {outstanding_allocation_indices:?}"
);
value
}
fn record_alloc(ptr: *mut c_void) {
RECORDER.with(|recorder| {
if recorder.enabled.load(SeqCst) {
let count = recorder.allocation_count.fetch_add(1, SeqCst);
recorder
.outstanding_allocations
.lock()
.unwrap()
.insert(Allocation(ptr), count);
}
});
}
fn record_dealloc(ptr: *mut c_void) {
RECORDER.with(|recorder| {
if recorder.enabled.load(SeqCst) {
recorder
.outstanding_allocations
.lock()
.unwrap()
.remove(&Allocation(ptr));
}
});
}
unsafe extern "C" fn ts_record_malloc(size: usize) -> *mut c_void {
let result = malloc(size);
record_alloc(result);
result
}
unsafe extern "C" fn ts_record_calloc(count: usize, size: usize) -> *mut c_void {
let result = calloc(count, size);
record_alloc(result);
result
}
unsafe extern "C" fn ts_record_realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
let result = realloc(ptr, size);
if ptr.is_null() {
record_alloc(result);
} else if ptr != result {
record_dealloc(ptr);
record_alloc(result);
}
result
}
unsafe extern "C" fn ts_record_free(ptr: *mut c_void) {
record_dealloc(ptr);
free(ptr);
}

View file

@ -1,104 +0,0 @@
// For some reasons `Command::spawn` doesn't work in CI env for many exotic arches.
#![cfg(all(any(target_arch = "x86_64", target_arch = "x86"), not(sanitizing)))]
use std::{
env::VarError,
process::{Command, Stdio},
};
use tree_sitter::Parser;
use tree_sitter_generate::load_grammar_file;
use super::generate_parser;
use crate::tests::helpers::fixtures::{fixtures_dir, get_test_language};
// The `sanitizing` cfg is required to don't run tests under specific sunitizer
// because they don't work well with subprocesses _(it's an assumption)_.
//
// Below are two alternative examples of how to disable tests for some arches
// if a way with excluding the whole mod from compilation wouldn't work well.
//
// XXX: Also may be it makes sense to keep such tests as ignored by default
// to omit surprises and enable them on CI by passing an extra option explicitly:
//
// > cargo test -- --include-ignored
//
// #[cfg(all(any(target_arch = "x86_64", target_arch = "x86"), not(sanitizing)))]
// #[cfg_attr(not(all(any(target_arch = "x86_64", target_arch = "x86"), not(sanitizing))), ignore)]
//
#[test]
fn test_grammar_that_should_hang_and_not_segfault() {
let parent_sleep_millis = 1000;
let test_name = "test_grammar_that_should_hang_and_not_segfault";
let test_var = "CARGO_HANG_TEST";
eprintln!(" {test_name}");
let tests_exec_path = std::env::args()
.next()
.expect("Failed to get tests executable path");
match std::env::var(test_var) {
Ok(v) if v == test_name => {
eprintln!(" child process id {}", std::process::id());
hang_test();
}
Err(VarError::NotPresent) => {
eprintln!(" parent process id {}", std::process::id());
let mut command = Command::new(tests_exec_path);
command.arg(test_name).env(test_var, test_name);
if std::env::args().any(|x| x == "--nocapture") {
command.arg("--nocapture");
} else {
command.stdout(Stdio::null()).stderr(Stdio::null());
}
match command.spawn() {
Ok(mut child) => {
std::thread::sleep(std::time::Duration::from_millis(parent_sleep_millis));
match child.try_wait() {
Ok(Some(status)) if status.success() => {
panic!("Child didn't hang and exited successfully")
}
Ok(Some(status)) => panic!(
"Child didn't hang and exited with status code: {:?}",
status.code()
),
_ => (),
}
if let Err(e) = child.kill() {
eprintln!(
"Failed to kill hang test's process id: {}, error: {e}",
child.id()
);
}
}
Err(e) => panic!("{e}"),
}
}
Err(e) => panic!("Env var error: {e}"),
_ => unreachable!(),
}
}
fn hang_test() {
let test_grammar_dir = fixtures_dir()
.join("test_grammars")
.join("get_col_should_hang_not_crash");
let grammar_json = load_grammar_file(&test_grammar_dir.join("grammar.js"), None).unwrap();
let (parser_name, parser_code) = generate_parser(grammar_json.as_str()).unwrap();
let language = get_test_language(&parser_name, &parser_code, Some(test_grammar_dir.as_path()));
let mut parser = Parser::new();
parser.set_language(&language).unwrap();
let code_that_should_hang = "\nHello";
parser.parse(code_that_should_hang, None).unwrap();
}

View file

@ -1,70 +0,0 @@
use tree_sitter::Parser;
use tree_sitter_highlight::{Highlight, Highlighter};
use super::helpers::fixtures::{get_highlight_config, get_language, test_loader};
use crate::{
query_testing::{parse_position_comments, Assertion, Utf8Point},
test_highlight::get_highlight_positions,
};
#[test]
fn test_highlight_test_with_basic_test() {
let language = get_language("javascript");
let config = get_highlight_config(
"javascript",
Some("injections.scm"),
&[
"function".to_string(),
"variable".to_string(),
"keyword".to_string(),
],
);
let source = [
"// hi",
"var abc = function(d) {",
" // ^ function",
" // ^^^ keyword",
" return d + e;",
" // ^ variable",
" // ^ !variable",
"};",
"var y̆y̆y̆y̆ = function() {}",
" // ^ function",
" // ^ keyword",
]
.join("\n");
let assertions =
parse_position_comments(&mut Parser::new(), &language, source.as_bytes()).unwrap();
assert_eq!(
assertions,
&[
Assertion::new(1, 5, 1, false, String::from("function")),
Assertion::new(1, 11, 3, false, String::from("keyword")),
Assertion::new(4, 9, 1, false, String::from("variable")),
Assertion::new(4, 11, 1, true, String::from("variable")),
Assertion::new(8, 5, 1, false, String::from("function")),
Assertion::new(8, 11, 1, false, String::from("keyword")),
]
);
let mut highlighter = Highlighter::new();
let highlight_positions =
get_highlight_positions(test_loader(), &mut highlighter, &config, source.as_bytes())
.unwrap();
assert_eq!(
highlight_positions,
&[
(Utf8Point::new(1, 0), Utf8Point::new(1, 3), Highlight(2)), // "var"
(Utf8Point::new(1, 4), Utf8Point::new(1, 7), Highlight(0)), // "abc"
(Utf8Point::new(1, 10), Utf8Point::new(1, 18), Highlight(2)), // "function"
(Utf8Point::new(1, 19), Utf8Point::new(1, 20), Highlight(1)), // "d"
(Utf8Point::new(4, 2), Utf8Point::new(4, 8), Highlight(2)), // "return"
(Utf8Point::new(4, 9), Utf8Point::new(4, 10), Highlight(1)), // "d"
(Utf8Point::new(4, 13), Utf8Point::new(4, 14), Highlight(1)), // "e"
(Utf8Point::new(8, 0), Utf8Point::new(8, 3), Highlight(2)), // "var"
(Utf8Point::new(8, 4), Utf8Point::new(8, 8), Highlight(0)), // "y̆y̆y̆y̆"
(Utf8Point::new(8, 11), Utf8Point::new(8, 19), Highlight(2)), // "function"
]
);
}

View file

@ -1,264 +0,0 @@
use std::{fs, path::PathBuf, process::Command};
use anyhow::{anyhow, Context, Result};
use regex::Regex;
use tree_sitter_loader::TreeSitterJSON;
pub struct Version {
pub version: String,
pub current_dir: PathBuf,
}
impl Version {
#[must_use]
pub const fn new(version: String, current_dir: PathBuf) -> Self {
Self {
version,
current_dir,
}
}
pub fn run(self) -> Result<()> {
let tree_sitter_json = self.current_dir.join("tree-sitter.json");
let tree_sitter_json =
serde_json::from_str::<TreeSitterJSON>(&fs::read_to_string(tree_sitter_json)?)?;
let is_multigrammar = tree_sitter_json.grammars.len() > 1;
self.update_treesitter_json().with_context(|| {
format!(
"Failed to update tree-sitter.json at {}",
self.current_dir.display()
)
})?;
self.update_cargo_toml().with_context(|| {
format!(
"Failed to update Cargo.toml at {}",
self.current_dir.display()
)
})?;
self.update_package_json().with_context(|| {
format!(
"Failed to update package.json at {}",
self.current_dir.display()
)
})?;
self.update_makefile(is_multigrammar).with_context(|| {
format!(
"Failed to update Makefile at {}",
self.current_dir.display()
)
})?;
self.update_cmakelists_txt().with_context(|| {
format!(
"Failed to update CMakeLists.txt at {}",
self.current_dir.display()
)
})?;
self.update_pyproject_toml().with_context(|| {
format!(
"Failed to update pyproject.toml at {}",
self.current_dir.display()
)
})?;
Ok(())
}
fn update_treesitter_json(&self) -> Result<()> {
let tree_sitter_json = &fs::read_to_string(self.current_dir.join("tree-sitter.json"))?;
let tree_sitter_json = tree_sitter_json
.lines()
.map(|line| {
if line.contains("\"version\":") {
let prefix_index = line.find("\"version\":").unwrap() + "\"version\":".len();
let start_quote = line[prefix_index..].find('"').unwrap() + prefix_index + 1;
let end_quote = line[start_quote + 1..].find('"').unwrap() + start_quote + 1;
format!(
"{}{}{}",
&line[..start_quote],
self.version,
&line[end_quote..]
)
} else {
line.to_string()
}
})
.collect::<Vec<_>>()
.join("\n")
+ "\n";
fs::write(self.current_dir.join("tree-sitter.json"), tree_sitter_json)?;
Ok(())
}
fn update_cargo_toml(&self) -> Result<()> {
if !self.current_dir.join("Cargo.toml").exists() {
return Ok(());
}
let cargo_toml = fs::read_to_string(self.current_dir.join("Cargo.toml"))?;
let cargo_toml = cargo_toml
.lines()
.map(|line| {
if line.starts_with("version =") {
format!("version = \"{}\"", self.version)
} else {
line.to_string()
}
})
.collect::<Vec<_>>()
.join("\n")
+ "\n";
fs::write(self.current_dir.join("Cargo.toml"), cargo_toml)?;
if self.current_dir.join("Cargo.lock").exists() {
let Ok(cmd) = Command::new("cargo")
.arg("generate-lockfile")
.arg("--offline")
.current_dir(&self.current_dir)
.output()
else {
return Ok(()); // cargo is not `executable`, ignore
};
if !cmd.status.success() {
let stderr = String::from_utf8_lossy(&cmd.stderr);
return Err(anyhow!(
"Failed to run `cargo generate-lockfile`:\n{stderr}"
));
}
}
Ok(())
}
fn update_package_json(&self) -> Result<()> {
if !self.current_dir.join("package.json").exists() {
return Ok(());
}
let package_json = &fs::read_to_string(self.current_dir.join("package.json"))?;
let package_json = package_json
.lines()
.map(|line| {
if line.contains("\"version\":") {
let prefix_index = line.find("\"version\":").unwrap() + "\"version\":".len();
let start_quote = line[prefix_index..].find('"').unwrap() + prefix_index + 1;
let end_quote = line[start_quote + 1..].find('"').unwrap() + start_quote + 1;
format!(
"{}{}{}",
&line[..start_quote],
self.version,
&line[end_quote..]
)
} else {
line.to_string()
}
})
.collect::<Vec<_>>()
.join("\n")
+ "\n";
fs::write(self.current_dir.join("package.json"), package_json)?;
if self.current_dir.join("package-lock.json").exists() {
let Ok(cmd) = Command::new("npm")
.arg("install")
.arg("--package-lock-only")
.current_dir(&self.current_dir)
.output()
else {
return Ok(()); // npm is not `executable`, ignore
};
if !cmd.status.success() {
let stderr = String::from_utf8_lossy(&cmd.stderr);
return Err(anyhow!("Failed to run `npm install`:\n{stderr}"));
}
}
Ok(())
}
fn update_makefile(&self, is_multigrammar: bool) -> Result<()> {
let makefile = if is_multigrammar {
if !self.current_dir.join("common").join("common.mak").exists() {
return Ok(());
}
fs::read_to_string(self.current_dir.join("Makefile"))?
} else {
if !self.current_dir.join("Makefile").exists() {
return Ok(());
}
fs::read_to_string(self.current_dir.join("Makefile"))?
};
let makefile = makefile
.lines()
.map(|line| {
if line.starts_with("VERSION") {
format!("VERSION := {}", self.version)
} else {
line.to_string()
}
})
.collect::<Vec<_>>()
.join("\n")
+ "\n";
fs::write(self.current_dir.join("Makefile"), makefile)?;
Ok(())
}
fn update_cmakelists_txt(&self) -> Result<()> {
if !self.current_dir.join("CMakeLists.txt").exists() {
return Ok(());
}
let cmake = fs::read_to_string(self.current_dir.join("CMakeLists.txt"))?;
let re = Regex::new(r#"(\s*VERSION\s+)"[0-9]+\.[0-9]+\.[0-9]+""#)?;
let cmake = re.replace(&cmake, format!(r#"$1"{}""#, self.version));
fs::write(self.current_dir.join("CMakeLists.txt"), cmake.as_bytes())?;
Ok(())
}
fn update_pyproject_toml(&self) -> Result<()> {
if !self.current_dir.join("pyproject.toml").exists() {
return Ok(());
}
let pyproject_toml = fs::read_to_string(self.current_dir.join("pyproject.toml"))?;
let pyproject_toml = pyproject_toml
.lines()
.map(|line| {
if line.starts_with("version =") {
format!("version = \"{}\"", self.version)
} else {
line.to_string()
}
})
.collect::<Vec<_>>()
.join("\n")
+ "\n";
fs::write(self.current_dir.join("pyproject.toml"), pyproject_toml)?;
Ok(())
}
}

115
crates/cli/Cargo.toml Normal file
View file

@ -0,0 +1,115 @@
[package]
name = "tree-sitter-cli"
version.workspace = true
description = "CLI tool for developing, testing, and using Tree-sitter parsers"
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
homepage.workspace = true
repository.workspace = true
documentation = "https://docs.rs/tree-sitter-cli"
license.workspace = true
keywords.workspace = true
categories.workspace = true
include = [ "build.rs", "README.md", "LICENSE", "benches/*", "src/**" ]
[lints]
workspace = true
[lib]
path = "src/tree_sitter_cli.rs"
[[bin]]
doc = false
name = "tree-sitter"
path = "src/main.rs"
[[bench]]
harness = false
name = "benchmark"
[features]
default = [ "qjs-rt" ]
qjs-rt = [ "tree-sitter-generate/qjs-rt" ]
wasm = [ "tree-sitter/wasm", "tree-sitter-loader/wasm" ]
[dependencies]
ansi_colours.workspace = true
anstyle.workspace = true
anyhow.workspace = true
bstr.workspace = true
clap.workspace = true
clap_complete.workspace = true
clap_complete_nushell.workspace = true
crc32fast.workspace = true
ctor.workspace = true
ctrlc.workspace = true
dialoguer.workspace = true
glob.workspace = true
heck.workspace = true
html-escape.workspace = true
indoc.workspace = true
log.workspace = true
memchr.workspace = true
rand.workspace = true
regex.workspace = true
schemars.workspace = true
semver.workspace = true
serde.workspace = true
serde_json.workspace = true
similar.workspace = true
streaming-iterator.workspace = true
thiserror.workspace = true
tiny_http.workspace = true
walkdir.workspace = true
wasmparser.workspace = true
webbrowser.workspace = true
tree-sitter.workspace = true
tree-sitter-config.workspace = true
tree-sitter-generate = { features = [ "load" ], workspace = true }
tree-sitter-highlight.workspace = true
tree-sitter-loader.workspace = true
tree-sitter-tags.workspace = true
[dev-dependencies]
encoding_rs = "0.8.35"
tree_sitter_proc_macro = { package = "tree-sitter-tests-proc-macro", path = "src/tests/proc_macro" }
widestring = "1.2.1"
pretty_assertions.workspace = true
tempfile.workspace = true
unindent.workspace = true
[package.metadata.binstall]
pkg-fmt = "zip"
[package.metadata.binstall.overrides.aarch64-unknown-linux-gnu]
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-linux-arm64{ archive-suffix }"
[package.metadata.binstall.overrides.armv7-unknown-linux-gnueabihf]
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-linux-arm{ archive-suffix }"
[package.metadata.binstall.overrides.x86_64-unknown-linux-gnu]
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-linux-x64{ archive-suffix }"
[package.metadata.binstall.overrides.i686-unknown-linux-gnu]
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-linux-x86{ archive-suffix }"
[package.metadata.binstall.overrides.powerpc64-unknown-linux-gnu]
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-linux-powerpc64{ archive-suffix }"
[package.metadata.binstall.overrides.aarch64-pc-windows-msvc]
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-windows-arm64{ archive-suffix }"
[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-windows-x64{ archive-suffix }"
[package.metadata.binstall.overrides.i686-pc-windows-msvc]
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-windows-x86{ archive-suffix }"
[package.metadata.binstall.overrides.aarch64-apple-darwin]
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-macos-arm64{ archive-suffix }"
[package.metadata.binstall.overrides.x86_64-apple-darwin]
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-macos-x64{ archive-suffix }"

21
crates/cli/LICENSE Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Max Brunsfeld
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -7,20 +7,20 @@
[npmjs.com]: https://www.npmjs.org/package/tree-sitter-cli
[npmjs.com badge]: https://img.shields.io/npm/v/tree-sitter-cli.svg?color=%23BF4A4A
The Tree-sitter CLI allows you to develop, test, and use Tree-sitter grammars from the command line. It works on `MacOS`, `Linux`, and `Windows`.
The Tree-sitter CLI allows you to develop, test, and use Tree-sitter grammars from the command line. It works on `MacOS`,
`Linux`, and `Windows`.
### Installation
You can install the `tree-sitter-cli` with `cargo`:
You can install the `tree-sitter-cli` with [`cargo-binstall`](https://github.com/cargo-bins/cargo-binstall):
```sh
cargo install --locked tree-sitter-cli
cargo binstall tree-sitter-cli
```
or with `npm`:
or you can build it from source:
```sh
npm install tree-sitter-cli
cargo install --locked tree-sitter-cli
```
You can also download a pre-built binary for your platform from [the releases page].
@ -34,9 +34,11 @@ The `tree-sitter` binary itself has no dependencies, but specific commands have
### Commands
* `generate` - The `tree-sitter generate` command will generate a Tree-sitter parser based on the grammar in the current working directory. See [the documentation] for more information.
* `generate` - The `tree-sitter generate` command will generate a Tree-sitter parser based on the grammar in the current
working directory. See [the documentation] for more information.
* `test` - The `tree-sitter test` command will run the unit tests for the Tree-sitter parser in the current working directory. See [the documentation] for more information.
* `test` - The `tree-sitter test` command will run the unit tests for the Tree-sitter parser in the current working directory.
See [the documentation] for more information.
* `parse` - The `tree-sitter parse` command will parse a file (or list of files) using Tree-sitter parsers.

View file

@ -2,13 +2,15 @@ use std::{
collections::BTreeMap,
env, fs,
path::{Path, PathBuf},
str,
sync::LazyLock,
time::Instant,
};
use anyhow::Context;
use log::info;
use tree_sitter::{Language, Parser, Query};
#[cfg(feature = "wasm")]
use tree_sitter::{WasmStore, wasmtime};
use tree_sitter_loader::{CompileConfig, Loader};
include!("../src/tests/helpers/dirs.rs");
@ -18,14 +20,18 @@ static LANGUAGE_FILTER: LazyLock<Option<String>> =
static EXAMPLE_FILTER: LazyLock<Option<String>> =
LazyLock::new(|| env::var("TREE_SITTER_BENCHMARK_EXAMPLE_FILTER").ok());
static REPETITION_COUNT: LazyLock<usize> = LazyLock::new(|| {
env::var("TREE_SITTER_BENCHMARK_REPETITION_COUNT")
.map(|s| s.parse::<usize>().unwrap())
.unwrap_or(5)
env::var("TREE_SITTER_BENCHMARK_REPETITION_COUNT").map_or(5, |s| s.parse::<usize>().unwrap())
});
static WASM: LazyLock<bool> = LazyLock::new(|| env::var_os("TREE_SITTER_BENCHMARK_WASM").is_some());
static TEST_LOADER: LazyLock<Loader> =
LazyLock::new(|| Loader::with_parser_lib_path(SCRATCH_DIR.clone()));
#[cfg(feature = "wasm")]
static WASM_ENGINE: LazyLock<wasmtime::Engine> = LazyLock::new(Default::default);
#[allow(clippy::type_complexity)]
#[expect(
clippy::type_complexity,
reason = "complex map type reflects benchmark data structure"
)]
static EXAMPLE_AND_QUERY_PATHS_BY_LANGUAGE_DIR: LazyLock<
BTreeMap<PathBuf, (Vec<PathBuf>, Vec<PathBuf>)>,
> = LazyLock::new(|| {
@ -37,22 +43,14 @@ static EXAMPLE_AND_QUERY_PATHS_BY_LANGUAGE_DIR: LazyLock<
if let Ok(example_files) = fs::read_dir(dir.join("examples")) {
example_paths.extend(example_files.filter_map(|p| {
let p = p.unwrap().path();
if p.is_file() {
Some(p)
} else {
None
}
if p.is_file() { Some(p) } else { None }
}));
}
if let Ok(query_files) = fs::read_dir(dir.join("queries")) {
query_paths.extend(query_files.filter_map(|p| {
let p = p.unwrap().path();
if p.is_file() {
Some(p)
} else {
None
}
if p.is_file() { Some(p) } else { None }
}));
}
} else {
@ -71,6 +69,8 @@ static EXAMPLE_AND_QUERY_PATHS_BY_LANGUAGE_DIR: LazyLock<
});
fn main() {
tree_sitter_cli::logger::init();
let max_path_length = EXAMPLE_AND_QUERY_PATHS_BY_LANGUAGE_DIR
.values()
.flat_map(|(e, q)| {
@ -81,7 +81,7 @@ fn main() {
.max()
.unwrap_or(0);
eprintln!("Benchmarking with {} repetitions", *REPETITION_COUNT);
info!("Benchmarking with {} repetitions", *REPETITION_COUNT);
let mut parser = Parser::new();
let mut all_normal_speeds = Vec::new();
@ -92,38 +92,42 @@ fn main() {
{
let language_name = language_path.file_name().unwrap().to_str().unwrap();
if let Some(filter) = LANGUAGE_FILTER.as_ref() {
if language_name != filter.as_str() {
continue;
}
if let Some(filter) = LANGUAGE_FILTER.as_ref()
&& language_name != filter.as_str()
{
continue;
}
eprintln!("\nLanguage: {language_name}");
let language = get_language(language_path);
info!("\nLanguage: {language_name}");
let language = if *WASM {
get_wasm_language(language_name, &mut parser)
} else {
get_language(language_path)
};
parser.set_language(&language).unwrap();
eprintln!(" Constructing Queries");
info!(" Constructing Queries");
for path in query_paths {
if let Some(filter) = EXAMPLE_FILTER.as_ref() {
if !path.to_str().unwrap().contains(filter.as_str()) {
continue;
}
if let Some(filter) = EXAMPLE_FILTER.as_ref()
&& !path.to_str().unwrap().contains(filter.as_str())
{
continue;
}
parse(path, max_path_length, |source| {
Query::new(&language, str::from_utf8(source).unwrap())
.with_context(|| format!("Query file path: {path:?}"))
.with_context(|| format!("Query file path: {}", path.display()))
.expect("Failed to parse query");
});
}
eprintln!(" Parsing Valid Code:");
info!(" Parsing Valid Code:");
let mut normal_speeds = Vec::new();
for example_path in example_paths {
if let Some(filter) = EXAMPLE_FILTER.as_ref() {
if !example_path.to_str().unwrap().contains(filter.as_str()) {
continue;
}
if let Some(filter) = EXAMPLE_FILTER.as_ref()
&& !example_path.to_str().unwrap().contains(filter.as_str())
{
continue;
}
normal_speeds.push(parse(example_path, max_path_length, |code| {
@ -131,17 +135,17 @@ fn main() {
}));
}
eprintln!(" Parsing Invalid Code (mismatched languages):");
info!(" Parsing Invalid Code (mismatched languages):");
let mut error_speeds = Vec::new();
for (other_language_path, (example_paths, _)) in
EXAMPLE_AND_QUERY_PATHS_BY_LANGUAGE_DIR.iter()
{
if other_language_path != language_path {
for example_path in example_paths {
if let Some(filter) = EXAMPLE_FILTER.as_ref() {
if !example_path.to_str().unwrap().contains(filter.as_str()) {
continue;
}
if let Some(filter) = EXAMPLE_FILTER.as_ref()
&& !example_path.to_str().unwrap().contains(filter.as_str())
{
continue;
}
error_speeds.push(parse(example_path, max_path_length, |code| {
@ -152,30 +156,30 @@ fn main() {
}
if let Some((average_normal, worst_normal)) = aggregate(&normal_speeds) {
eprintln!(" Average Speed (normal): {average_normal} bytes/ms");
eprintln!(" Worst Speed (normal): {worst_normal} bytes/ms");
info!(" Average Speed (normal): {average_normal} bytes/ms");
info!(" Worst Speed (normal): {worst_normal} bytes/ms");
}
if let Some((average_error, worst_error)) = aggregate(&error_speeds) {
eprintln!(" Average Speed (errors): {average_error} bytes/ms");
eprintln!(" Worst Speed (errors): {worst_error} bytes/ms");
info!(" Average Speed (errors): {average_error} bytes/ms");
info!(" Worst Speed (errors): {worst_error} bytes/ms");
}
all_normal_speeds.extend(normal_speeds);
all_error_speeds.extend(error_speeds);
}
eprintln!("\n Overall");
info!("\n Overall");
if let Some((average_normal, worst_normal)) = aggregate(&all_normal_speeds) {
eprintln!(" Average Speed (normal): {average_normal} bytes/ms");
eprintln!(" Worst Speed (normal): {worst_normal} bytes/ms");
info!(" Average Speed (normal): {average_normal} bytes/ms");
info!(" Worst Speed (normal): {worst_normal} bytes/ms");
}
if let Some((average_error, worst_error)) = aggregate(&all_error_speeds) {
eprintln!(" Average Speed (errors): {average_error} bytes/ms");
eprintln!(" Worst Speed (errors): {worst_error} bytes/ms");
info!(" Average Speed (errors): {average_error} bytes/ms");
info!(" Worst Speed (errors): {worst_error} bytes/ms");
}
eprintln!();
info!("");
}
fn aggregate(speeds: &[usize]) -> Option<(usize, usize)> {
@ -194,14 +198,8 @@ fn aggregate(speeds: &[usize]) -> Option<(usize, usize)> {
}
fn parse(path: &Path, max_path_length: usize, mut action: impl FnMut(&[u8])) -> usize {
eprint!(
" {:width$}\t",
path.file_name().unwrap().to_str().unwrap(),
width = max_path_length
);
let source_code = fs::read(path)
.with_context(|| format!("Failed to read {path:?}"))
.with_context(|| format!("Failed to read {}", path.display()))
.unwrap();
let time = Instant::now();
for _ in 0..*REPETITION_COUNT {
@ -210,8 +208,9 @@ fn parse(path: &Path, max_path_length: usize, mut action: impl FnMut(&[u8])) ->
let duration = time.elapsed() / (*REPETITION_COUNT as u32);
let duration_ns = duration.as_nanos();
let speed = ((source_code.len() as u128) * 1_000_000) / duration_ns;
eprintln!(
"time {:>7.2} ms\t\tspeed {speed:>6} bytes/ms",
info!(
" {:max_path_length$}\ttime {:>7.2} ms\t\tspeed {speed:>6} bytes/ms",
path.file_name().unwrap().to_str().unwrap(),
(duration_ns as f64) / 1e6,
);
speed as usize
@ -221,6 +220,35 @@ fn get_language(path: &Path) -> Language {
let src_path = GRAMMARS_DIR.join(path).join("src");
TEST_LOADER
.load_language_at_path(CompileConfig::new(&src_path, None, None))
.with_context(|| format!("Failed to load language at path {src_path:?}"))
.with_context(|| format!("Failed to load language at path {}", src_path.display()))
.unwrap()
}
#[cfg(feature = "wasm")]
fn get_wasm_language(language_name: &str, parser: &mut Parser) -> Language {
let wasm_language_name = language_name.replace('-', "_");
let wasm_path = ROOT_DIR
.join("target")
.join("release")
.join(format!("tree-sitter-{language_name}.wasm"));
let wasm = fs::read(&wasm_path)
.with_context(|| {
format!(
"Failed to read {}. Generate Wasm fixtures with `cargo xtask generate-fixtures --wasm`",
wasm_path.display()
)
})
.unwrap();
let mut store = WasmStore::new(&WASM_ENGINE).expect("Failed to create Wasm store");
let language = store
.load_language(&wasm_language_name, &wasm)
.with_context(|| format!("Failed to load Wasm language at {}", wasm_path.display()))
.unwrap();
parser.set_wasm_store(store).unwrap();
language
}
#[cfg(not(feature = "wasm"))]
fn get_wasm_language(_language_name: &str, _parser: &mut Parser) -> Language {
panic!("Wasm benchmarking requires the `wasm` feature");
}

View file

@ -52,16 +52,14 @@ fn main() {
fn web_playground_files_present() -> bool {
let paths = [
"../docs/src/assets/js/playground.js",
"../lib/binding_web/tree-sitter.js",
"../lib/binding_web/tree-sitter.wasm",
"../../docs/src/assets/js/playground.js",
"../../lib/binding_web/web-tree-sitter.js",
"../../lib/binding_web/web-tree-sitter.wasm",
];
paths.iter().all(|p| Path::new(p).exists())
}
// When updating this function, don't forget to also update generate/build.rs which has a
// near-identical function.
fn read_git_sha() -> Option<String> {
let crate_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());

1
crates/cli/eslint/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
LICENSE

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,8 @@
"description": "Eslint configuration for Tree-sitter grammar files",
"repository": {
"type": "git",
"url": "git+https://github.com/tree-sitter/tree-sitter.git"
"url": "git+https://github.com/tree-sitter/tree-sitter.git",
"directory": "crates/cli/eslint"
},
"license": "MIT",
"author": "Amaan Qureshi <amaanq12@gmail.com>",
@ -16,9 +17,13 @@
"tree-sitter"
],
"dependencies": {
"eslint-plugin-jsdoc": "^50.2.4"
"eslint-plugin-jsdoc": "^62.7.0"
},
"peerDependencies": {
"eslint": ">= 9"
},
"scripts": {
"prepack": "cp ../../../LICENSE .",
"postpack": "rm LICENSE"
}
}

View file

@ -3,17 +3,22 @@ type BlankRule = { type: 'BLANK' };
type ChoiceRule = { type: 'CHOICE'; members: Rule[] };
type FieldRule = { type: 'FIELD'; name: string; content: Rule };
type ImmediateTokenRule = { type: 'IMMEDIATE_TOKEN'; content: Rule };
type PatternRule = { type: 'PATTERN'; value: string };
type PatternRule = { type: 'PATTERN'; value: string; flags?: string };
type PrecedenceValue = string | number;
type PrecDynamicRule = { type: 'PREC_DYNAMIC'; content: Rule; value: number };
type PrecLeftRule = { type: 'PREC_LEFT'; content: Rule; value: number };
type PrecRightRule = { type: 'PREC_RIGHT'; content: Rule; value: number };
type PrecRule = { type: 'PREC'; content: Rule; value: number };
type PrecLeftRule = { type: 'PREC_LEFT'; content: Rule; value: PrecedenceValue };
type PrecRightRule = { type: 'PREC_RIGHT'; content: Rule; value: PrecedenceValue };
type PrecRule = { type: 'PREC'; content: Rule; value: PrecedenceValue };
type Repeat1Rule = { type: 'REPEAT1'; content: Rule };
type RepeatRule = { type: 'REPEAT'; content: Rule };
type ReservedRule = { type: 'RESERVED'; content: Rule; context_name: string };
type SeqRule = { type: 'SEQ'; members: Rule[] };
type StringRule = { type: 'STRING'; value: string };
type SymbolRule<Name extends string> = { type: 'SYMBOL'; name: Name };
type PrecedenceEntry = StringRule | SymbolRule<string>;
type TokenRule = { type: 'TOKEN'; content: Rule };
type EOFRule = { type: 'EOF' };
type Rule =
| AliasRule
@ -28,17 +33,17 @@ type Rule =
| PrecRule
| Repeat1Rule
| RepeatRule
| ReservedRule
| SeqRule
| StringRule
| SymbolRule<string>
| TokenRule;
| TokenRule
| EOFRule;
class RustRegex {
declare class RustRegex {
value: string;
constructor(pattern: string) {
this.value = pattern;
}
constructor(pattern: string);
}
type RuleOrLiteral = Rule | RegExp | RustRegex | string;
@ -86,8 +91,8 @@ interface Grammar<
*/
precedences?: (
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
previous: Rule[][],
) => RuleOrLiteral[][],
previous: PrecedenceEntry[][],
) => (string | PrecedenceEntry)[][],
/**
* An array of arrays of rule names. Each inner array represents a set of
@ -101,8 +106,8 @@ interface Grammar<
*/
conflicts?: (
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
previous: Rule[][],
) => RuleOrLiteral[][];
previous: SymbolRule<string>[][],
) => SymbolRule<string>[][];
/**
* An array of token names which can be returned by an _external scanner_.
@ -127,9 +132,11 @@ interface Grammar<
* specify extras: `$ => []` in your grammar.
*
* @param $ grammar rules
* @param previous array of extras from the base grammar
*/
extras?: (
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
previous: Rule[],
) => RuleOrLiteral[];
/**
@ -142,8 +149,8 @@ interface Grammar<
*/
inline?: (
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
previous: Rule[],
) => RuleOrLiteral[];
previous: SymbolRule<string>[],
) => SymbolRule<string>[];
/**
* A list of hidden rule names that should be considered supertypes in the
@ -155,8 +162,8 @@ interface Grammar<
*/
supertypes?: (
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
previous: Rule[],
) => RuleOrLiteral[];
previous: SymbolRule<string>[],
) => SymbolRule<string>[];
/**
* The name of a token that will match keywords for the purpose of the
@ -166,13 +173,47 @@ interface Grammar<
*
* @see https://tree-sitter.github.io/tree-sitter/creating-parsers/3-writing-the-grammar#keyword-extraction
*/
word?: ($: GrammarSymbols<RuleName | BaseGrammarRuleName>) => RuleOrLiteral;
word?: (
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
) => SymbolRule<string>;
/**
* Mapping of names to reserved word sets. The first reserved word set is the
* global word set, meaning it applies to every rule in every parse state.
* The other word sets can be used with the `reserved` function. Each callback
* receives the base grammar's reserved word set of the same name as its second
* argument, or `undefined` if no matching set exists.
*/
reserved?: Record<
string,
(
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
previous: Rule[] | undefined,
) => RuleOrLiteral[]
>;
}
/**
* Return type of grammar(). The runtime evaluates and normalizes the grammar
* beneath a "grammar" key. Optional input fields become required output fields
* with default values when not provided.
*/
type GrammarSchema<RuleName extends string> = {
[K in keyof Grammar<RuleName>]: K extends 'rules'
? Record<RuleName, Rule>
: Grammar<RuleName>[K];
grammar: {
name: string;
/** Base grammar name when extending; undefined for root grammars. */
inherits: string | undefined;
rules: Record<RuleName, Rule>;
precedences: PrecedenceEntry[][];
conflicts: string[][];
externals: Rule[];
extras: Rule[];
inline: string[];
supertypes: string[];
word: string | undefined;
reserved: Record<string, Rule[]>;
};
};
/**
@ -251,7 +292,7 @@ declare function optional(rule: RuleOrLiteral): ChoiceRule;
* @see https://docs.oracle.com/cd/E19504-01/802-5880/6i9k05dh3/index.html
*/
declare const prec: {
(value: String | number, rule: RuleOrLiteral): PrecRule;
(value: string | number, rule: RuleOrLiteral): PrecRule;
/**
* Marks the given rule as left-associative (and optionally applies a
@ -267,7 +308,7 @@ declare const prec: {
* @see https://docs.oracle.com/cd/E19504-01/802-5880/6i9k05dh3/index.html
*/
left(rule: RuleOrLiteral): PrecLeftRule;
left(value: String | number, rule: RuleOrLiteral): PrecLeftRule;
left(value: string | number, rule: RuleOrLiteral): PrecLeftRule;
/**
* Marks the given rule as right-associative (and optionally applies a
@ -283,7 +324,7 @@ declare const prec: {
* @see https://docs.oracle.com/cd/E19504-01/802-5880/6i9k05dh3/index.html
*/
right(rule: RuleOrLiteral): PrecRightRule;
right(value: String | number, rule: RuleOrLiteral): PrecRightRule;
right(value: string | number, rule: RuleOrLiteral): PrecRightRule;
/**
* Marks the given rule with a numerical precedence which will be used to
@ -300,7 +341,7 @@ declare const prec: {
*
* @see https://www.gnu.org/software/bison/manual/html_node/Generalized-LR-Parsing.html
*/
dynamic(value: String | number, rule: RuleOrLiteral): PrecDynamicRule;
dynamic(value: number, rule: RuleOrLiteral): PrecDynamicRule;
};
/**
@ -320,6 +361,15 @@ declare function repeat(rule: RuleOrLiteral): RepeatRule;
*/
declare function repeat1(rule: RuleOrLiteral): Repeat1Rule;
/**
* Overrides the global reserved word set for a given rule. The word set name
* should be defined in the `reserved` field in the grammar.
*
* @param wordset name of the reserved word set
* @param rule rule that will use the reserved word set
*/
declare function reserved(wordset: string, rule: RuleOrLiteral): ReservedRule;
/**
* Creates a rule that matches any number of other rules, one after another.
* It is analogous to simply writing multiple symbols next to each other
@ -338,7 +388,7 @@ declare function sym<Name extends string>(name: Name): SymbolRule<Name>;
/**
* Marks the given rule as producing only a single token. Tree-sitter's
* default is to treat each String or RegExp literal in the grammar as a
* default is to treat each string or RegExp literal in the grammar as a
* separate token. Each token is matched separately by the lexer and
* returned as its own leaf node in the tree. The token function allows
* you to express a complex rule using the DSL functions (rather
@ -362,6 +412,19 @@ declare const token: {
immediate(rule: RuleOrLiteral): ImmediateTokenRule;
};
/**
* Matches the end of input. May only appear as the final symbol of a
* (possibly nested) sequence; a production ending in `eof()` reduces only
* when the lookahead is end-of-input, rather than shifting a token.
*
* Choice branches that continue past `eof()` are dropped as unreachable,
* and `eof()` is not allowed inside `token()`.
*
* Useful when a rule should match either an explicit terminator (e.g. a
* newline) or the end of the file.
*/
declare function eof(): EOFRule;
/**
* Creates a new language grammar with the provided schema.
*

3
cli/npm/install.js → crates/cli/npm/install.js Executable file → Normal file
View file

@ -6,7 +6,8 @@ const http = require('http');
const https = require('https');
const packageJSON = require('./package.json');
// Look to a results table in https://github.com/tree-sitter/tree-sitter/issues/2196
https.globalAgent.keepAlive = false;
const matrix = {
platform: {
'darwin': {

Some files were not shown because too many files have changed in this diff Show more