mirror of
https://github.com/tree-sitter/tree-sitter.git
synced 2026-09-10 07:36:22 -04:00
Compare commits
151 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d97971e245 | ||
|
|
d2436dc57c | ||
|
|
e323709b8d | ||
|
|
c7a65b67d1 | ||
|
|
9d1f31602c | ||
|
|
f837fc9813 | ||
|
|
15adfa9e51 | ||
|
|
063f8f886b | ||
|
|
6320165515 | ||
|
|
808e4b1fc0 | ||
|
|
7566ffacb7 | ||
|
|
4b7e2c956c | ||
|
|
3ee7c639de | ||
|
|
fd0ccd65a5 | ||
|
|
8df22e6f0e | ||
|
|
c02f32485a | ||
|
|
f9b9b39075 | ||
|
|
09384230b4 | ||
|
|
5ccdbb6b84 | ||
|
|
fcb38e9dcf | ||
|
|
b6243a5234 | ||
|
|
64402de285 | ||
|
|
2194ac1a9c | ||
|
|
a2b8369c4a | ||
|
|
1ae3cc5753 | ||
|
|
90dd1a0cfb | ||
|
|
fa6222a0d3 | ||
|
|
77b741bb83 | ||
|
|
1c21ff33d6 | ||
|
|
5756ced350 | ||
|
|
eca86a7a6c | ||
|
|
306f868299 | ||
|
|
e7d6651336 | ||
|
|
074b6eb05b | ||
|
|
4ac1514d94 | ||
|
|
3fc4cd21bc | ||
|
|
568aee0b7e | ||
|
|
2efce10fe9 | ||
|
|
e8b9639291 | ||
|
|
576564d25a | ||
|
|
fbf3049dc0 | ||
|
|
99bc36b857 | ||
|
|
1ef1048d05 | ||
|
|
a61be4ac27 | ||
|
|
be8f380ad4 | ||
|
|
d4e89a4881 | ||
|
|
e502c6c18e | ||
|
|
2fddf5a1b4 | ||
|
|
70068dd487 | ||
|
|
323d99ede3 | ||
|
|
3ab78c3c5b | ||
|
|
f4b1fe2ba7 | ||
|
|
7f534862c3 | ||
|
|
77b96de523 | ||
|
|
a082228e43 | ||
|
|
7aea01521d | ||
|
|
2cad8b8d47 | ||
|
|
ddbe46956f | ||
|
|
17f9796925 | ||
|
|
17e4bf92c0 | ||
|
|
ec120d06f2 | ||
|
|
7c3d842519 | ||
|
|
b7964b9b19 | ||
|
|
b0ddae770e | ||
|
|
0b9a7f87ee | ||
|
|
e4ac513afd | ||
|
|
2b00a9b7fc | ||
|
|
c64e7f0f7b | ||
|
|
7952172109 | ||
|
|
8185030b48 | ||
|
|
8850e11bd8 | ||
|
|
a4bdd941d5 | ||
|
|
89f553c2e9 | ||
|
|
cd5b087cd9 | ||
|
|
c0d1444118 | ||
|
|
0b04fd0533 | ||
|
|
05cf9a161a | ||
|
|
bab48517d7 | ||
|
|
e28cb5ae74 | ||
|
|
3839f6fcf5 | ||
|
|
001a926d56 | ||
|
|
6f2e8a6cf4 | ||
|
|
0ae615883e | ||
|
|
9ce156713c | ||
|
|
365b1f0f91 | ||
|
|
8e87144b61 | ||
|
|
b61eabb4d2 | ||
|
|
c802b44dff | ||
|
|
16c7bfb48f | ||
|
|
d01bd9b1e5 | ||
|
|
594f9d5580 | ||
|
|
534c4a074c | ||
|
|
0de6ea6edd | ||
|
|
6fdf7fdbc6 | ||
|
|
a6aabeb941 | ||
|
|
b1e10d5410 | ||
|
|
b3e86f75cc | ||
|
|
a226c68720 | ||
|
|
d03bb288e2 | ||
|
|
ed06ca058b | ||
|
|
fc5f1b4526 | ||
|
|
054f892546 | ||
|
|
4bcc064ecc | ||
|
|
93a5767892 | ||
|
|
a5129d5bc0 | ||
|
|
9a22dff7e5 | ||
|
|
fdc20d9c58 | ||
|
|
89e804b7e4 | ||
|
|
3b85287d6d | ||
|
|
79b60a271c | ||
|
|
ad7a629a1b | ||
|
|
325bc50d6f | ||
|
|
932bde72b2 | ||
|
|
470813116b | ||
|
|
7ec1794d6b | ||
|
|
fa8811f7f7 | ||
|
|
ef4999bf61 | ||
|
|
77e43dd116 | ||
|
|
666144d3ed | ||
|
|
ce2cb41e1f | ||
|
|
a423343bd3 | ||
|
|
c8aedb8cfa | ||
|
|
308b96d927 | ||
|
|
e98a09b6cc | ||
|
|
d3a20faff9 | ||
|
|
88a5475496 | ||
|
|
6a8a5e33d9 | ||
|
|
bc2e4e2386 | ||
|
|
f44e86628a | ||
|
|
ed6e42cbf0 | ||
|
|
4809aaaf04 | ||
|
|
152d2756fc | ||
|
|
f05efbb352 | ||
|
|
1f221c8500 | ||
|
|
fdca0718bc | ||
|
|
fa7b1b2a66 | ||
|
|
adcc4d1f7b | ||
|
|
7d9c544c96 | ||
|
|
c1e49d1571 | ||
|
|
eae6554735 | ||
|
|
48ee942c4f | ||
|
|
9ee2b87dd6 | ||
|
|
fb91deb8d9 | ||
|
|
789a966f96 | ||
|
|
3c49fef0e3 | ||
|
|
8a297b86bc | ||
|
|
ac6644016c | ||
|
|
a80765614b | ||
|
|
34602af22c | ||
|
|
c4f81931e6 | ||
|
|
25777e5a64 |
2
.dockerignore
Normal file
2
.dockerignore
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
target
|
||||
.git
|
||||
5
.envrc
5
.envrc
|
|
@ -1,4 +1 @@
|
|||
source_up_if_exists
|
||||
if [ -z "${IN_NIX_SHELL:-}" ]; then
|
||||
use flake .
|
||||
fi
|
||||
use flake
|
||||
|
|
|
|||
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -2,14 +2,6 @@ name: Bug Report
|
|||
description: Report a problem
|
||||
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"
|
||||
|
|
|
|||
8
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
8
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
|
|
@ -2,14 +2,6 @@ name: Feature request
|
|||
description: Request an 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: |
|
||||
|
|
|
|||
3
.github/actions/cache/action.yml
vendored
3
.github/actions/cache/action.yml
vendored
|
|
@ -21,9 +21,6 @@ runs:
|
|||
'lib/src/parser.h',
|
||||
'lib/src/array.h',
|
||||
'lib/src/alloc.h',
|
||||
'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
20
.github/cliff.toml
vendored
|
|
@ -43,16 +43,16 @@ commit_preprocessors = [
|
|||
]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ 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 = ".*" },
|
||||
{ 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" },
|
||||
]
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = false
|
||||
|
|
|
|||
6
.github/pull_request_template.md
vendored
6
.github/pull_request_template.md
vendored
|
|
@ -1,6 +0,0 @@
|
|||
### 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. -->
|
||||
16
.github/scripts/reviewers_remove.js
vendored
Normal file
16
.github/scripts/reviewers_remove.js
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
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,
|
||||
});
|
||||
};
|
||||
35
.github/scripts/wasm_stdlib.js
vendored
35
.github/scripts/wasm_stdlib.js
vendored
|
|
@ -1,35 +0,0 @@
|
|||
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.`);
|
||||
};
|
||||
9
.github/workflows/backport.yml
vendored
9
.github/workflows/backport.yml
vendored
|
|
@ -14,20 +14,17 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ github.event.pull_request.base.ref }}
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Create app token
|
||||
uses: actions/create-github-app-token@v3
|
||||
uses: actions/create-github-app-token@v2
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.BACKPORT_APP }}
|
||||
private-key: ${{ secrets.BACKPORT_KEY }}
|
||||
|
||||
- name: Create backport PR
|
||||
uses: korthout/backport-action@v4.6.0
|
||||
uses: korthout/backport-action@v3
|
||||
with:
|
||||
pull_title: "${pull_title}"
|
||||
label_pattern: "^ci:backport ([^ ]+)$"
|
||||
|
|
|
|||
2
.github/workflows/bindgen.yml
vendored
2
.github/workflows/bindgen.yml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up stable Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
|
|
|||
43
.github/workflows/build.yml
vendored
43
.github/workflows/build.yml
vendored
|
|
@ -26,7 +26,6 @@ jobs:
|
|||
- windows-x86
|
||||
- macos-arm64
|
||||
- macos-x64
|
||||
- illumos-x64
|
||||
- wasm32
|
||||
|
||||
include:
|
||||
|
|
@ -46,9 +45,6 @@ jobs:
|
|||
- { platform: macos-x64 , target: x86_64-apple-darwin , os: macos-15-intel }
|
||||
- { platform: wasm32 , target: wasm32-unknown-unknown , os: ubuntu-24.04 }
|
||||
|
||||
# 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 }
|
||||
|
||||
# Extra features
|
||||
- { platform: linux-arm64 , features: wasm }
|
||||
- { platform: linux-x64 , features: wasm , run-wasm-test: true }
|
||||
|
|
@ -73,7 +69,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up cross-compilation
|
||||
if: matrix.cross
|
||||
|
|
@ -95,7 +91,7 @@ jobs:
|
|||
|
||||
- name: Cache Emscripten SDK
|
||||
if: contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test)
|
||||
uses: actions/cache@v6
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: emsdk
|
||||
key: emsdk-${{ env.EMSCRIPTEN_VERSION }}-${{ runner.os }}-${{ runner.arch }}
|
||||
|
|
@ -116,22 +112,17 @@ jobs:
|
|||
|
||||
- name: Set up Node.js
|
||||
if: contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test)
|
||||
uses: actions/setup-node@v7.0.0
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
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 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: |
|
||||
|
|
@ -201,7 +192,7 @@ jobs:
|
|||
WASMTIME_REPO: https://github.com/bytecodealliance/wasmtime
|
||||
|
||||
- name: Build C library (make)
|
||||
if: runner.os != 'Windows' && !matrix.vm
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
if [[ $PLATFORM == linux-arm ]]; then
|
||||
CC=arm-linux-gnueabihf-gcc; AR=arm-linux-gnueabihf-ar
|
||||
|
|
@ -218,7 +209,7 @@ jobs:
|
|||
CFLAGS: -g -Werror -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types -Werror=strict-aliasing -Wstrict-aliasing=2
|
||||
|
||||
- name: Build C library (CMake)
|
||||
if: "!matrix.cross && !matrix.vm"
|
||||
if: "!matrix.cross"
|
||||
run: |
|
||||
cmake -S . -B build/static \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
|
|
@ -237,20 +228,6 @@ jobs:
|
|||
CC: ${{ contains(matrix.platform, 'linux') && 'clang' || '' }}
|
||||
WASM: ${{ contains(matrix.features, 'wasm') && (matrix.run-wasm-test || !inputs.run-test) && 'ON' || 'OFF' }}
|
||||
|
||||
- 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
|
||||
|
|
@ -269,7 +246,7 @@ jobs:
|
|||
run: cargo check --no-default-features --target='${{ matrix.target }}'
|
||||
|
||||
- name: Build target
|
||||
if: "!inputs.run-test && !matrix.vm"
|
||||
if: "!inputs.run-test"
|
||||
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' || '' }}
|
||||
|
|
@ -299,13 +276,9 @@ jobs:
|
|||
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 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
|
||||
if: "!inputs.run-test && !matrix.no-run"
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: tree-sitter.${{ matrix.platform }}
|
||||
path: target/${{ matrix.target }}/release/tree-sitter${{ contains(matrix.target, 'windows') && '.exe' || '' }}
|
||||
|
|
@ -314,7 +287,7 @@ jobs:
|
|||
|
||||
- name: Upload Wasm artifacts
|
||||
if: "!inputs.run-test && matrix.platform == 'linux-x64'"
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: tree-sitter.wasm
|
||||
path: |
|
||||
|
|
|
|||
43
.github/workflows/ci.yml
vendored
43
.github/workflows/ci.yml
vendored
|
|
@ -1,14 +1,21 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'release-[0-9]+.[0-9]+'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'release-[0-9]+.[0-9]+'
|
||||
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
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
|
@ -19,7 +26,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up stable Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
|
@ -27,25 +34,13 @@ jobs:
|
|||
toolchain: stable
|
||||
components: clippy, rustfmt
|
||||
|
||||
- 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
|
||||
- name: Lint files
|
||||
run: |
|
||||
make lint
|
||||
make lint-web
|
||||
|
||||
sanitize:
|
||||
uses: ./.github/workflows/sanitize.yml
|
||||
|
||||
build:
|
||||
uses: ./.github/workflows/build.yml
|
||||
|
||||
check-wasm-stdlib:
|
||||
uses: ./.github/workflows/wasm_stdlib.yml
|
||||
|
|
|
|||
2
.github/workflows/crate_versions.yml
vendored
2
.github/workflows/crate_versions.yml
vendored
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
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
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
|
|
|||
16
.github/workflows/docs.yml
vendored
16
.github/workflows/docs.yml
vendored
|
|
@ -16,29 +16,35 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- 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/tags/v0.5.4 --jq "$jq_expr")
|
||||
url=$(gh api repos/rust-lang/mdbook/releases/tags/v0.4.52 --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@v6
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v5
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: docs/book
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v5
|
||||
uses: actions/deploy-pages@v4
|
||||
|
|
|
|||
67
.github/workflows/nvim_ts.yml
vendored
67
.github/workflows/nvim_ts.yml
vendored
|
|
@ -3,7 +3,6 @@ name: nvim-treesitter parser tests
|
|||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'lib/**'
|
||||
- 'crates/cli/**'
|
||||
- 'crates/config/**'
|
||||
- 'crates/generate/**'
|
||||
|
|
@ -26,54 +25,34 @@ jobs:
|
|||
name: ${{ matrix.os }} - ${{ matrix.type }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
NVIM: ${{ matrix.os == 'windows-latest' && 'nvim.exe' || 'nvim' }}
|
||||
NVIM_TAG: stable
|
||||
NVIM_DIR: neovim
|
||||
NVIM: ${{ matrix.os == 'windows-latest' && 'nvim-win64\\bin\\nvim.exe' || 'nvim' }}
|
||||
NVIM_TS_DIR: nvim-treesitter
|
||||
steps:
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
- run: cargo build --profile optimize
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- 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
|
||||
- uses: actions/checkout@v6
|
||||
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=10
|
||||
run: $NVIM -l ./scripts/install-parsers.lua --generate --max-jobs=2
|
||||
working-directory: ${{ env.NVIM_TS_DIR }}
|
||||
shell: bash
|
||||
|
||||
|
|
@ -84,13 +63,7 @@ jobs:
|
|||
shell: bash
|
||||
|
||||
- if: "!cancelled()"
|
||||
name: Test parsers
|
||||
run: $NVIM -l ./scripts/check-parsers.lua
|
||||
working-directory: ${{ env.NVIM_TS_DIR }}
|
||||
shell: bash
|
||||
|
||||
- if: "!cancelled()"
|
||||
name: Test queries
|
||||
name: Check query files
|
||||
run: $NVIM -l ./scripts/check-queries.lua
|
||||
working-directory: ${{ env.NVIM_TS_DIR }}
|
||||
shell: bash
|
||||
|
|
|
|||
42
.github/workflows/release.yml
vendored
42
.github/workflows/release.yml
vendored
|
|
@ -1,14 +1,7 @@
|
|||
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]+
|
||||
|
|
@ -28,22 +21,11 @@ jobs:
|
|||
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@v7.0.1
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
|
|
@ -70,24 +52,16 @@ jobs:
|
|||
ls -l target/
|
||||
|
||||
- name: Generate attestations
|
||||
uses: actions/attest-build-provenance@v4
|
||||
uses: actions/attest-build-provenance@v3
|
||||
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 ${{ env.TAG_NAME }} $PRERELEASE \
|
||||
gh release create $GITHUB_REF_NAME \
|
||||
target/tree-sitter-*.gz \
|
||||
target/tree-sitter-cli-*.zip \
|
||||
target/web-tree-sitter.tar.gz
|
||||
|
|
@ -96,7 +70,6 @@ jobs:
|
|||
|
||||
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:
|
||||
|
|
@ -105,7 +78,7 @@ jobs:
|
|||
needs: release
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
|
@ -121,7 +94,6 @@ jobs:
|
|||
|
||||
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:
|
||||
|
|
@ -134,10 +106,10 @@ jobs:
|
|||
directory: [crates/cli/npm, lib/binding_web]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v7.0.0
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
|
|
|||
8
.github/workflows/response.yml
vendored
8
.github/workflows/response.yml
vendored
|
|
@ -17,13 +17,13 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout script
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
sparse-checkout: .github/scripts/close_unresponsive.js
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Run script
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@v8
|
||||
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@v7.0.1
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
sparse-checkout: .github/scripts/remove_response_label.js
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Run script
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const script = require('./.github/scripts/remove_response_label.js')
|
||||
|
|
|
|||
26
.github/workflows/reviewers_remove.yml
vendored
26
.github/workflows/reviewers_remove.yml
vendored
|
|
@ -11,21 +11,15 @@ jobs:
|
|||
remove-reviewers:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Remove reviewers
|
||||
uses: actions/github-script@v9
|
||||
- name: Checkout script
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
sparse-checkout: .github/scripts/reviewers_remove.js
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Run script
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
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,
|
||||
});
|
||||
const script = require('./.github/scripts/reviewers_remove.js')
|
||||
await script({github, context})
|
||||
|
|
|
|||
2
.github/workflows/sanitize.yml
vendored
2
.github/workflows/sanitize.yml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
TREE_SITTER: ${{ github.workspace }}/target/release/tree-sitter
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install UBSAN library
|
||||
run: sudo apt-get update -y && sudo apt-get install -y libubsan1
|
||||
|
|
|
|||
4
.github/workflows/spam.yml
vendored
4
.github/workflows/spam.yml
vendored
|
|
@ -16,13 +16,13 @@ jobs:
|
|||
if: github.event.label.name == 'spam'
|
||||
steps:
|
||||
- name: Checkout script
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
sparse-checkout: .github/scripts/close_spam.js
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Run script
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const script = require('./.github/scripts/close_spam.js')
|
||||
|
|
|
|||
2
.github/workflows/wasm_exports.yml
vendored
2
.github/workflows/wasm_exports.yml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up stable Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
|
|
|||
19
.github/workflows/wasm_stdlib.yml
vendored
19
.github/workflows/wasm_stdlib.yml
vendored
|
|
@ -1,19 +0,0 @@
|
|||
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 });
|
||||
19
.taplo.toml
19
.taplo.toml
|
|
@ -1,19 +0,0 @@
|
|||
[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
|
||||
11
.zed/settings.json
Normal file
11
.zed/settings.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"lsp": {
|
||||
"rust-analyzer": {
|
||||
"initialization_options": {
|
||||
"cargo": {
|
||||
"features": "all"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
project(tree-sitter
|
||||
VERSION "0.28.0"
|
||||
VERSION "0.26.13"
|
||||
DESCRIPTION "An incremental parsing system for programming tools"
|
||||
HOMEPAGE_URL "https://tree-sitter.github.io/tree-sitter/"
|
||||
LANGUAGES C)
|
||||
|
|
|
|||
BIN
Cargo.lock
generated
BIN
Cargo.lock
generated
Binary file not shown.
125
Cargo.toml
125
Cargo.toml
|
|
@ -1,5 +1,5 @@
|
|||
[workspace]
|
||||
default-members = [ "crates/cli" ]
|
||||
default-members = ["crates/cli"]
|
||||
members = [
|
||||
"crates/cli",
|
||||
"crates/config",
|
||||
|
|
@ -14,22 +14,25 @@ members = [
|
|||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
authors = [ "Max Brunsfeld <maxbrunsfeld@gmail.com>", "Amaan Qureshi <amaanq12@gmail.com>" ]
|
||||
categories = [ "command-line-utilities", "parsing" ]
|
||||
edition = "2024"
|
||||
version = "0.26.13"
|
||||
authors = [
|
||||
"Max Brunsfeld <maxbrunsfeld@gmail.com>",
|
||||
"Amaan Qureshi <amaanq12@gmail.com>",
|
||||
]
|
||||
edition = "2021"
|
||||
rust-version = "1.84"
|
||||
homepage = "https://tree-sitter.github.io/tree-sitter"
|
||||
keywords = [ "incremental", "parsing" ]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tree-sitter/tree-sitter"
|
||||
rust-version = "1.90"
|
||||
version = "0.28.0"
|
||||
license = "MIT"
|
||||
keywords = ["incremental", "parsing"]
|
||||
categories = ["command-line-utilities", "parsing"]
|
||||
|
||||
[workspace.lints.clippy]
|
||||
cargo = { level = "warn", priority = -1 }
|
||||
dbg_macro = "deny"
|
||||
nursery = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
todo = "deny"
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
nursery = { level = "warn", priority = -1 }
|
||||
cargo = { level = "warn", priority = -1 }
|
||||
|
||||
# The lints below are a specific subset of the pedantic+nursery lints
|
||||
# that we explicitly allow in the tree-sitter codebase because they either:
|
||||
|
|
@ -38,31 +41,52 @@ todo = "deny"
|
|||
# 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"
|
||||
needless_for_each = "allow"
|
||||
obfuscated_if_else = "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"
|
||||
tuple_array_conversions = "allow"
|
||||
unnecessary_wraps = "allow"
|
||||
unused_self = "allow"
|
||||
used_underscore_items = "allow"
|
||||
|
||||
[workspace.lints.rust]
|
||||
mismatched_lifetime_syntaxes = "allow"
|
||||
|
||||
[profile.optimize]
|
||||
inherits = "release"
|
||||
codegen-units = 1 # Maximum size reduction optimizations.
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
lto = true # Link-time optimization.
|
||||
opt-level = 3 # Optimization level 3.
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
codegen-units = 1 # Maximum size reduction optimizations.
|
||||
|
||||
[profile.size]
|
||||
inherits = "optimize"
|
||||
|
|
@ -70,69 +94,70 @@ opt-level = "s" # Optimize for size.
|
|||
|
||||
[profile.release-dev]
|
||||
inherits = "release"
|
||||
codegen-units = 256
|
||||
lto = false
|
||||
debug = true
|
||||
debug-assertions = true
|
||||
incremental = true
|
||||
lto = false
|
||||
overflow-checks = true
|
||||
incremental = true
|
||||
codegen-units = 256
|
||||
|
||||
[workspace.dependencies]
|
||||
ansi_colours = "1.2.3"
|
||||
anstyle = "1.0.14"
|
||||
anyhow = "1.0.102"
|
||||
bstr = "1.12.1"
|
||||
cc = "1.2.63"
|
||||
clap = { features = [
|
||||
anstyle = "1.0.13"
|
||||
anyhow = "1.0.100"
|
||||
bstr = "1.12.0"
|
||||
cc = "1.2.48"
|
||||
clap = { version = "4.5.53", features = [
|
||||
"cargo",
|
||||
"derive",
|
||||
"env",
|
||||
"help",
|
||||
"string",
|
||||
"unstable-styles",
|
||||
], version = "4.5.58" }
|
||||
clap_complete = "4.6.3"
|
||||
] }
|
||||
clap_complete = "4.5.61"
|
||||
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" }
|
||||
ctor = "0.2.9"
|
||||
ctrlc = { version = "3.5.0", features = ["termination"] }
|
||||
dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
|
||||
etcetera = "0.11.0"
|
||||
fs4 = "0.12.0"
|
||||
glob = "0.3.3"
|
||||
hashbrown = { default-features = false, version = "0.17.1" }
|
||||
heck = "0.5.0"
|
||||
html-escape = "0.2.13"
|
||||
indexmap = "2.13.0"
|
||||
indoc = "2.0.7"
|
||||
indexmap = "2.12.1"
|
||||
indoc = "2.0.6"
|
||||
libloading = "0.9.0"
|
||||
log = { features = [ "std" ], version = "0.4.30" }
|
||||
memchr = "2.8.1"
|
||||
log = { version = "0.4.28", features = ["std"] }
|
||||
memchr = "2.7.6"
|
||||
once_cell = "1.21.3"
|
||||
pretty_assertions = "1.4.1"
|
||||
rand = "0.10.1"
|
||||
regex = "1.12.3"
|
||||
regex-syntax = "0.8.9"
|
||||
rand = "0.8.5"
|
||||
regex = "1.11.3"
|
||||
regex-syntax = "0.8.6"
|
||||
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" }
|
||||
schemars = "1.0.5"
|
||||
semver = { version = "1.0.27", features = ["serde"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = { version = "1.0.145", features = ["preserve_order"] }
|
||||
similar = "2.7.0"
|
||||
smallbitvec = "2.6.0"
|
||||
streaming-iterator = "0.1.9"
|
||||
tempfile = "3.25.0"
|
||||
thiserror = "2.0.18"
|
||||
tempfile = "3.23.0"
|
||||
thiserror = "2.0.17"
|
||||
tiny_http = "0.12.0"
|
||||
topological-sort = "0.2.2"
|
||||
unindent = "0.2.4"
|
||||
walkdir = "2.5.0"
|
||||
wasmparser = "0.244.0"
|
||||
webbrowser = "1.2.1"
|
||||
wasmparser = "0.243.0"
|
||||
webbrowser = "1.0.5"
|
||||
|
||||
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 = { version = "0.26.13", path = "./lib" }
|
||||
tree-sitter-generate = { version = "0.26.13", path = "./crates/generate", default-features = false }
|
||||
tree-sitter-loader = { version = "0.26.13", path = "./crates/loader" }
|
||||
tree-sitter-config = { version = "0.26.13", path = "./crates/config" }
|
||||
tree-sitter-highlight = { version = "0.26.13", path = "./crates/highlight" }
|
||||
tree-sitter-tags = { version = "0.26.13", path = "./crates/tags" }
|
||||
|
||||
tree-sitter-language = { path = "./crates/language", version = "0.1.8" }
|
||||
tree-sitter-language = { version = "0.1", path = "./crates/language" }
|
||||
|
|
|
|||
10
Dockerfile
Normal file
10
Dockerfile
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
FROM rust:1.76-buster
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD cargo test --all-features
|
||||
7
Makefile
7
Makefile
|
|
@ -1,4 +1,4 @@
|
|||
VERSION := 0.28.0
|
||||
VERSION := 0.26.13
|
||||
DESCRIPTION := An incremental parsing system for programming tools
|
||||
HOMEPAGE_URL := https://tree-sitter.github.io/tree-sitter/
|
||||
|
||||
|
|
@ -129,13 +129,8 @@ 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 fmt --all
|
||||
taplo format
|
||||
|
||||
changelog:
|
||||
@git-cliff --config .github/cliff.toml --prepend CHANGELOG.md --latest --github-token $(shell gh auth token)
|
||||
|
|
|
|||
35
Package.swift
Normal file
35
Package.swift
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// 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"),
|
||||
.define("_BSD_SOURCE"),
|
||||
.define("_DARWIN_C_SOURCE"),
|
||||
]),
|
||||
],
|
||||
cLanguageStandard: .c11
|
||||
)
|
||||
53
build.zig
53
build.zig
|
|
@ -4,56 +4,51 @@ pub fn build(b: *std.Build) !void {
|
|||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
var threaded: std.Io.Threaded = .init(b.allocator, .{});
|
||||
defer threaded.deinit();
|
||||
const io = threaded.io();
|
||||
|
||||
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 tree_sitter = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true,
|
||||
.pic = if (shared) true else null,
|
||||
});
|
||||
const lib: *std.Build.Step.Compile = b.addLibrary(.{
|
||||
.name = "tree-sitter",
|
||||
.linkage = if (shared) .dynamic else .static,
|
||||
.root_module = tree_sitter,
|
||||
.root_module = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true,
|
||||
.pic = if (shared) true else null,
|
||||
}),
|
||||
});
|
||||
|
||||
if (amalgamated) {
|
||||
tree_sitter.addCSourceFile(.{
|
||||
lib.addCSourceFile(.{
|
||||
.file = b.path("lib/src/lib.c"),
|
||||
.flags = &.{"-std=c11"},
|
||||
});
|
||||
} else {
|
||||
const files = try findSourceFiles(b, io);
|
||||
const files = try findSourceFiles(b);
|
||||
defer b.allocator.free(files);
|
||||
tree_sitter.addCSourceFiles(.{
|
||||
lib.addCSourceFiles(.{
|
||||
.root = b.path("lib/src"),
|
||||
.files = files,
|
||||
.flags = &.{"-std=c11"},
|
||||
});
|
||||
}
|
||||
|
||||
tree_sitter.addIncludePath(b.path("lib/include"));
|
||||
tree_sitter.addIncludePath(b.path("lib/src"));
|
||||
tree_sitter.addIncludePath(b.path("lib/src/wasm"));
|
||||
lib.addIncludePath(b.path("lib/include"));
|
||||
lib.addIncludePath(b.path("lib/src"));
|
||||
lib.addIncludePath(b.path("lib/src/wasm"));
|
||||
|
||||
tree_sitter.addCMacro("_POSIX_C_SOURCE", "200112L");
|
||||
tree_sitter.addCMacro("_DEFAULT_SOURCE", "");
|
||||
tree_sitter.addCMacro("_BSD_SOURCE", "");
|
||||
tree_sitter.addCMacro("_DARWIN_C_SOURCE", "");
|
||||
lib.root_module.addCMacro("_POSIX_C_SOURCE", "200112L");
|
||||
lib.root_module.addCMacro("_DEFAULT_SOURCE", "");
|
||||
lib.root_module.addCMacro("_BSD_SOURCE", "");
|
||||
lib.root_module.addCMacro("_DARWIN_C_SOURCE", "");
|
||||
|
||||
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", .{});
|
||||
lib.root_module.addCMacro("TREE_SITTER_FEATURE_WASM", "");
|
||||
lib.addSystemIncludePath(wasmtime.path("include"));
|
||||
lib.addLibraryPath(wasmtime.path("lib"));
|
||||
if (shared) lib.linkSystemLibrary("wasmtime");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,14 +122,14 @@ pub fn wasmtimeDep(target: std.Target) []const u8 {
|
|||
);
|
||||
}
|
||||
|
||||
fn findSourceFiles(b: *std.Build, io: std.Io) ![]const []const u8 {
|
||||
fn findSourceFiles(b: *std.Build) ![]const []const u8 {
|
||||
var sources: std.ArrayListUnmanaged([]const u8) = .empty;
|
||||
|
||||
var dir = try b.build_root.handle.openDir(io, "lib/src", .{ .iterate = true });
|
||||
var dir = try b.build_root.handle.openDir("lib/src", .{ .iterate = true });
|
||||
var iter = dir.iterate();
|
||||
defer dir.close(io);
|
||||
defer dir.close();
|
||||
|
||||
while (try iter.next(io)) |entry| {
|
||||
while (try iter.next()) |entry| {
|
||||
if (entry.kind != .file) continue;
|
||||
const file = entry.name;
|
||||
const ext = std.fs.path.extension(file);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
.{
|
||||
.name = .tree_sitter,
|
||||
.fingerprint = 0x841224b447ac0d4f,
|
||||
.version = "0.28.0",
|
||||
.minimum_zig_version = "0.16.0",
|
||||
.version = "0.26.13",
|
||||
.minimum_zig_version = "0.14.1",
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
|
|
@ -13,83 +13,83 @@
|
|||
},
|
||||
.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",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.14/wasmtime-v36.0.14-aarch64-android-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAK1DGQSFd3QeAM37CBAgGjcO6SP4PTDitKUyv2FL",
|
||||
.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",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.14/wasmtime-v36.0.14-aarch64-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AADHpGQQi5Sb9gmPvUbAP3eDfMT7SloTyWHq0bULy",
|
||||
.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",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.14/wasmtime-v36.0.14-aarch64-macos-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAA--3gIS9QGKTh_CdihUxebSyDLRgMyaSLErr0uU",
|
||||
.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",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.14/wasmtime-v36.0.14-aarch64-musl-c-api.tar.xz",
|
||||
.hash = "N-V-__8AALs2GATaXyZRY1eSj0MqcnCPA-rvwQKDLzNKoO7k",
|
||||
.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",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.14/wasmtime-v36.0.14-aarch64-windows-c-api.zip",
|
||||
.hash = "N-V-__8AACG7oQSQzo4Yqb2ROtmf0Te6z25qE2D1zZVOhOjT",
|
||||
.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",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.14/wasmtime-v36.0.14-armv7-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AACfgaQOJMJ60s9tEEnIGqCc5oCc8DjV6WCCU8jfv",
|
||||
.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",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.14/wasmtime-v36.0.14-i686-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAA0YpwPx6WET15rgQX6MuRYgJrBFFhGJ6T1o7bXd",
|
||||
.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",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.14/wasmtime-v36.0.14-i686-windows-c-api.zip",
|
||||
.hash = "N-V-__8AAEuShgR9nTTtALm1vg7ydAa-XHeFJoCeS6EwWMYO",
|
||||
.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",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.14/wasmtime-v36.0.14-riscv64gc-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AALE0UwVQ2O0JUsq-bvNiae9w9rF-DcoZReRwc3VO",
|
||||
.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",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.14/wasmtime-v36.0.14-s390x-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AADt6VAT4U8GJtiOuTxT_d--hYS7I8sOYeMDhsmYm",
|
||||
.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",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.14/wasmtime-v36.0.14-x86_64-android-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAOOKmQR9Gt_MNxWAdciCw9I0GLtvDhlcgC3dd8DU",
|
||||
.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",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.14/wasmtime-v36.0.14-x86_64-linux-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAB_LmASYgb6KhwBhZV3_1VCPN_qIBCiLONea2VGO",
|
||||
.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",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.14/wasmtime-v36.0.14-x86_64-macos-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAG9NVQNTKddBttoabwkDR_z9SjWpxC60kIzHiLZ5",
|
||||
.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",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.14/wasmtime-v36.0.14-x86_64-mingw-c-api.zip",
|
||||
.hash = "N-V-__8AABqH-QT-RZQZvYQr6KKwTgDYN36-F3zEld6zltCe",
|
||||
.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",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.14/wasmtime-v36.0.14-x86_64-musl-c-api.tar.xz",
|
||||
.hash = "N-V-__8AAHE1nATox7TV8A9MUpHA3T7VMRW__JIGXvwtBBnd",
|
||||
.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",
|
||||
.url = "https://github.com/bytecodealliance/wasmtime/releases/download/v36.0.14/wasmtime-v36.0.14-x86_64-windows-c-api.zip",
|
||||
.hash = "N-V-__8AAN9FXgVEOFW9RhCY3rKCYgg-HT7O8eBuFNOPI2Av",
|
||||
.lazy = true,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ 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
|
||||
documentation = "https://docs.rs/tree-sitter-cli"
|
||||
license.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
include = [ "build.rs", "README.md", "LICENSE", "benches/*", "src/**" ]
|
||||
include = ["build.rs", "README.md", "LICENSE", "benches/*", "src/**"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
@ -20,18 +21,18 @@ workspace = true
|
|||
path = "src/tree_sitter_cli.rs"
|
||||
|
||||
[[bin]]
|
||||
doc = false
|
||||
name = "tree-sitter"
|
||||
path = "src/main.rs"
|
||||
doc = false
|
||||
|
||||
[[bench]]
|
||||
harness = false
|
||||
name = "benchmark"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
default = [ "qjs-rt" ]
|
||||
qjs-rt = [ "tree-sitter-generate/qjs-rt" ]
|
||||
wasm = [ "tree-sitter/wasm", "tree-sitter-loader/wasm" ]
|
||||
default = ["qjs-rt"]
|
||||
wasm = ["tree-sitter/wasm", "tree-sitter-loader/wasm"]
|
||||
qjs-rt = ["tree-sitter-generate/qjs-rt"]
|
||||
|
||||
[dependencies]
|
||||
ansi_colours.workspace = true
|
||||
|
|
@ -66,19 +67,19 @@ wasmparser.workspace = true
|
|||
webbrowser.workspace = true
|
||||
|
||||
tree-sitter.workspace = true
|
||||
tree-sitter-generate = { workspace = true, features = ["load"] }
|
||||
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"
|
||||
tree_sitter_proc_macro = { path = "src/tests/proc_macro", package = "tree-sitter-tests-proc-macro" }
|
||||
|
||||
pretty_assertions.workspace = true
|
||||
tempfile.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
unindent.workspace = true
|
||||
|
||||
[package.metadata.binstall]
|
||||
|
|
|
|||
|
|
@ -7,12 +7,11 @@
|
|||
[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-binstall`](https://github.com/cargo-bins/cargo-binstall):
|
||||
You can install the `tree-sitter-cli` with `cargo-binstall`:
|
||||
|
||||
```sh
|
||||
cargo binstall tree-sitter-cli
|
||||
|
|
@ -34,11 +33,9 @@ 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use std::{
|
|||
collections::BTreeMap,
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
str,
|
||||
sync::LazyLock,
|
||||
time::Instant,
|
||||
};
|
||||
|
|
@ -9,8 +10,6 @@ use std::{
|
|||
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");
|
||||
|
|
@ -22,16 +21,10 @@ static EXAMPLE_FILTER: LazyLock<Option<String>> =
|
|||
static REPETITION_COUNT: LazyLock<usize> = LazyLock::new(|| {
|
||||
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);
|
||||
|
||||
#[expect(
|
||||
clippy::type_complexity,
|
||||
reason = "complex map type reflects benchmark data structure"
|
||||
)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
static EXAMPLE_AND_QUERY_PATHS_BY_LANGUAGE_DIR: LazyLock<
|
||||
BTreeMap<PathBuf, (Vec<PathBuf>, Vec<PathBuf>)>,
|
||||
> = LazyLock::new(|| {
|
||||
|
|
@ -43,14 +36,22 @@ 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 {
|
||||
|
|
@ -92,26 +93,22 @@ fn main() {
|
|||
{
|
||||
let language_name = language_path.file_name().unwrap().to_str().unwrap();
|
||||
|
||||
if let Some(filter) = LANGUAGE_FILTER.as_ref()
|
||||
&& language_name != filter.as_str()
|
||||
{
|
||||
continue;
|
||||
if let Some(filter) = LANGUAGE_FILTER.as_ref() {
|
||||
if language_name != filter.as_str() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
info!("\nLanguage: {language_name}");
|
||||
let language = if *WASM {
|
||||
get_wasm_language(language_name, &mut parser)
|
||||
} else {
|
||||
get_language(language_path)
|
||||
};
|
||||
let language = get_language(language_path);
|
||||
parser.set_language(&language).unwrap();
|
||||
|
||||
info!(" Constructing Queries");
|
||||
for path in query_paths {
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref()
|
||||
&& !path.to_str().unwrap().contains(filter.as_str())
|
||||
{
|
||||
continue;
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref() {
|
||||
if !path.to_str().unwrap().contains(filter.as_str()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
parse(path, max_path_length, |source| {
|
||||
|
|
@ -124,10 +121,10 @@ fn main() {
|
|||
info!(" Parsing Valid Code:");
|
||||
let mut normal_speeds = Vec::new();
|
||||
for example_path in example_paths {
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref()
|
||||
&& !example_path.to_str().unwrap().contains(filter.as_str())
|
||||
{
|
||||
continue;
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref() {
|
||||
if !example_path.to_str().unwrap().contains(filter.as_str()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
normal_speeds.push(parse(example_path, max_path_length, |code| {
|
||||
|
|
@ -142,10 +139,10 @@ fn main() {
|
|||
{
|
||||
if other_language_path != language_path {
|
||||
for example_path in example_paths {
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref()
|
||||
&& !example_path.to_str().unwrap().contains(filter.as_str())
|
||||
{
|
||||
continue;
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref() {
|
||||
if !example_path.to_str().unwrap().contains(filter.as_str()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
error_speeds.push(parse(example_path, max_path_length, |code| {
|
||||
|
|
@ -223,32 +220,3 @@ fn get_language(path: &Path) -> Language {
|
|||
.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");
|
||||
}
|
||||
|
|
|
|||
828
crates/cli/eslint/package-lock.json
generated
828
crates/cli/eslint/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -17,7 +17,7 @@
|
|||
"tree-sitter"
|
||||
],
|
||||
"dependencies": {
|
||||
"eslint-plugin-jsdoc": "^62.7.0"
|
||||
"eslint-plugin-jsdoc": "^50.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">= 9"
|
||||
|
|
|
|||
83
crates/cli/npm/dsl.d.ts
vendored
83
crates/cli/npm/dsl.d.ts
vendored
|
|
@ -3,22 +3,18 @@ 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; flags?: string };
|
||||
type PrecedenceValue = string | number;
|
||||
type PatternRule = { type: 'PATTERN'; value: string };
|
||||
type PrecDynamicRule = { type: 'PREC_DYNAMIC'; 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 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 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
|
||||
|
|
@ -37,8 +33,7 @@ type Rule =
|
|||
| SeqRule
|
||||
| StringRule
|
||||
| SymbolRule<string>
|
||||
| TokenRule
|
||||
| EOFRule;
|
||||
| TokenRule;
|
||||
|
||||
declare class RustRegex {
|
||||
value: string;
|
||||
|
|
@ -91,8 +86,8 @@ interface Grammar<
|
|||
*/
|
||||
precedences?: (
|
||||
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
|
||||
previous: PrecedenceEntry[][],
|
||||
) => (string | PrecedenceEntry)[][],
|
||||
previous: Rule[][],
|
||||
) => RuleOrLiteral[][],
|
||||
|
||||
/**
|
||||
* An array of arrays of rule names. Each inner array represents a set of
|
||||
|
|
@ -106,8 +101,8 @@ interface Grammar<
|
|||
*/
|
||||
conflicts?: (
|
||||
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
|
||||
previous: SymbolRule<string>[][],
|
||||
) => SymbolRule<string>[][];
|
||||
previous: Rule[][],
|
||||
) => RuleOrLiteral[][];
|
||||
|
||||
/**
|
||||
* An array of token names which can be returned by an _external scanner_.
|
||||
|
|
@ -132,11 +127,9 @@ 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[];
|
||||
|
||||
/**
|
||||
|
|
@ -149,8 +142,8 @@ interface Grammar<
|
|||
*/
|
||||
inline?: (
|
||||
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
|
||||
previous: SymbolRule<string>[],
|
||||
) => SymbolRule<string>[];
|
||||
previous: Rule[],
|
||||
) => RuleOrLiteral[];
|
||||
|
||||
/**
|
||||
* A list of hidden rule names that should be considered supertypes in the
|
||||
|
|
@ -162,8 +155,8 @@ interface Grammar<
|
|||
*/
|
||||
supertypes?: (
|
||||
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
|
||||
previous: SymbolRule<string>[],
|
||||
) => SymbolRule<string>[];
|
||||
previous: Rule[],
|
||||
) => RuleOrLiteral[];
|
||||
|
||||
/**
|
||||
* The name of a token that will match keywords for the purpose of the
|
||||
|
|
@ -173,47 +166,24 @@ interface Grammar<
|
|||
*
|
||||
* @see https://tree-sitter.github.io/tree-sitter/creating-parsers/3-writing-the-grammar#keyword-extraction
|
||||
*/
|
||||
word?: (
|
||||
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
|
||||
) => SymbolRule<string>;
|
||||
word?: ($: GrammarSymbols<RuleName | BaseGrammarRuleName>) => RuleOrLiteral;
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* The other word sets can be used with the `reserved` function.
|
||||
*/
|
||||
reserved?: Record<
|
||||
string,
|
||||
(
|
||||
$: GrammarSymbols<RuleName | BaseGrammarRuleName>,
|
||||
previous: Rule[] | undefined,
|
||||
) => RuleOrLiteral[]
|
||||
($: GrammarSymbols<RuleName | BaseGrammarRuleName>) => 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> = {
|
||||
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[]>;
|
||||
};
|
||||
[K in keyof Grammar<RuleName>]: K extends 'rules'
|
||||
? Record<RuleName, Rule>
|
||||
: Grammar<RuleName>[K];
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -341,7 +311,7 @@ declare const prec: {
|
|||
*
|
||||
* @see https://www.gnu.org/software/bison/manual/html_node/Generalized-LR-Parsing.html
|
||||
*/
|
||||
dynamic(value: number, rule: RuleOrLiteral): PrecDynamicRule;
|
||||
dynamic(value: string | number, rule: RuleOrLiteral): PrecDynamicRule;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -412,19 +382,6 @@ 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.
|
||||
*
|
||||
|
|
|
|||
4
crates/cli/npm/package-lock.json
generated
4
crates/cli/npm/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "tree-sitter-cli",
|
||||
"version": "0.28.0",
|
||||
"version": "0.26.13",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "tree-sitter-cli",
|
||||
"version": "0.28.0",
|
||||
"version": "0.26.13",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "tree-sitter-cli",
|
||||
"version": "0.28.0",
|
||||
"version": "0.26.13",
|
||||
"author": {
|
||||
"name": "Max Brunsfeld",
|
||||
"email": "maxbrunsfeld@gmail.com"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
installShellFiles,
|
||||
}:
|
||||
let
|
||||
canRunHost = stdenv.buildPlatform.canExecute stdenv.hostPlatform;
|
||||
isCross = stdenv.targetPlatform == stdenv.buildPlatform;
|
||||
in
|
||||
rustPlatform.buildRustPackage {
|
||||
pname = "tree-sitter-cli";
|
||||
|
|
@ -28,7 +28,7 @@ rustPlatform.buildRustPackage {
|
|||
pkg-config
|
||||
nodejs_22
|
||||
]
|
||||
++ lib.optionals canRunHost [ installShellFiles ];
|
||||
++ lib.optionals (!isCross) [ installShellFiles ];
|
||||
|
||||
cargoLock.lockFile = ../../Cargo.lock;
|
||||
|
||||
|
|
@ -42,9 +42,9 @@ rustPlatform.buildRustPackage {
|
|||
'';
|
||||
|
||||
preCheck = "export HOME=$TMPDIR";
|
||||
doCheck = canRunHost;
|
||||
doCheck = !isCross;
|
||||
|
||||
postInstall = lib.optionalString canRunHost ''
|
||||
postInstall = lib.optionalString (!isCross) ''
|
||||
installShellCompletion --cmd tree-sitter \
|
||||
--bash <($out/bin/tree-sitter complete --shell bash) \
|
||||
--zsh <($out/bin/tree-sitter complete --shell zsh) \
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use std::{
|
|||
};
|
||||
|
||||
use log::{error, info};
|
||||
use rand::RngExt;
|
||||
use rand::Rng;
|
||||
use regex::Regex;
|
||||
use tree_sitter::{Language, Parser};
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ use crate::{
|
|||
random::Rand,
|
||||
},
|
||||
parse::perform_edit,
|
||||
test::{DiffKey, TestDiff, TestEntry, TestExpectation, parse_tests, render_test_output},
|
||||
test::{parse_tests, strip_sexp_fields, DiffKey, TestDiff, TestEntry},
|
||||
};
|
||||
|
||||
pub static LOG_ENABLED: LazyLock<bool> = LazyLock::new(|| env::var("TREE_SITTER_LOG").is_ok());
|
||||
|
|
@ -63,9 +63,9 @@ fn regex_env_var(name: &'static str) -> Option<Regex> {
|
|||
#[must_use]
|
||||
pub fn new_seed() -> usize {
|
||||
int_env_var("TREE_SITTER_SEED").unwrap_or_else(|| {
|
||||
let mut rng = rand::rng();
|
||||
let seed = rng.random_range(0..=usize::MAX);
|
||||
eprintln!("fuzz seed: {seed}");
|
||||
let mut rng = rand::thread_rng();
|
||||
let seed = rng.gen::<usize>();
|
||||
info!("Seed: {seed}");
|
||||
seed
|
||||
})
|
||||
}
|
||||
|
|
@ -97,7 +97,9 @@ pub fn fuzz_language_corpus(
|
|||
.iter()
|
||||
.any(|lang| lang.as_ref() == language_name)
|
||||
}
|
||||
TestEntry::Group { children, .. } => {
|
||||
TestEntry::Group {
|
||||
ref mut children, ..
|
||||
} => {
|
||||
children.retain_mut(|child| retain(child, language_name));
|
||||
!children.is_empty()
|
||||
}
|
||||
|
|
@ -109,16 +111,12 @@ pub fn fuzz_language_corpus(
|
|||
let corpus_dir = grammar_dir.join(subdir).join("test").join("corpus");
|
||||
|
||||
if !corpus_dir.exists() || !corpus_dir.is_dir() {
|
||||
error!(
|
||||
"No corpus directory found, ensure that you have a `test/corpus` directory in your grammar directory with at least one test file."
|
||||
);
|
||||
error!("No corpus directory found, ensure that you have a `test/corpus` directory in your grammar directory with at least one test file.");
|
||||
return;
|
||||
}
|
||||
|
||||
if std::fs::read_dir(&corpus_dir).unwrap().count() == 0 {
|
||||
error!(
|
||||
"No corpus files found in `test/corpus`, ensure that you have at least one test file in your corpus directory."
|
||||
);
|
||||
error!("No corpus files found in `test/corpus`, ensure that you have at least one test file in your corpus directory.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +142,7 @@ pub fn fuzz_language_corpus(
|
|||
.take()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.chain(tests.iter().filter(|t| t.skip()).map(get_test_name))
|
||||
.chain(tests.iter().filter(|x| x.skip).map(get_test_name))
|
||||
.map(|x| (x, 0))
|
||||
.collect::<HashMap<String, usize>>();
|
||||
|
||||
|
|
@ -169,8 +167,31 @@ pub fn fuzz_language_corpus(
|
|||
println!(" {test_index}. {test_name}");
|
||||
|
||||
let passed = allocations::record_checked(|| {
|
||||
let check_output = !test.error();
|
||||
test.check_initial_parse(language, &test_name, check_output)
|
||||
let mut log_session = None;
|
||||
let mut parser = get_parser(&mut log_session, "log.html");
|
||||
parser.set_language(language).unwrap();
|
||||
set_included_ranges(&mut parser, &test.input, test.template_delimiters);
|
||||
|
||||
let tree = parser.parse(&test.input, None).unwrap();
|
||||
|
||||
if test.error {
|
||||
return true;
|
||||
}
|
||||
|
||||
let mut actual_output = tree.root_node().to_sexp();
|
||||
if !test.has_fields {
|
||||
actual_output = strip_sexp_fields(&actual_output);
|
||||
}
|
||||
|
||||
if actual_output != test.output {
|
||||
println!("Incorrect initial parse for {test_name}");
|
||||
DiffKey::print();
|
||||
println!("{}", TestDiff::new(&actual_output, &test.output));
|
||||
println!();
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
.unwrap_or_else(|e| {
|
||||
error!("{e}");
|
||||
|
|
@ -250,9 +271,12 @@ pub fn fuzz_language_corpus(
|
|||
let tree3 = parser.parse(&input, Some(&tree2)).unwrap();
|
||||
|
||||
// Verify that the final tree matches the expectation from the corpus.
|
||||
let actual_output = render_test_output(&input, &tree3, test.cst, test.has_fields).unwrap();
|
||||
let mut actual_output = tree3.root_node().to_sexp();
|
||||
if !test.has_fields {
|
||||
actual_output = strip_sexp_fields(&actual_output);
|
||||
}
|
||||
|
||||
if actual_output != test.output && !test.error() {
|
||||
if actual_output != test.output && !test.error {
|
||||
println!("Incorrect parse for {test_name} - seed {seed}");
|
||||
DiffKey::print();
|
||||
println!("{}", TestDiff::new(&actual_output, &test.output));
|
||||
|
|
@ -300,56 +324,12 @@ pub struct FlattenedTest {
|
|||
pub input: Vec<u8>,
|
||||
pub output: String,
|
||||
pub languages: Vec<Box<str>>,
|
||||
pub expectation: TestExpectation,
|
||||
pub error: bool,
|
||||
pub skip: bool,
|
||||
pub has_fields: bool,
|
||||
pub cst: bool,
|
||||
pub template_delimiters: Option<(&'static str, &'static str)>,
|
||||
}
|
||||
|
||||
impl FlattenedTest {
|
||||
#[must_use]
|
||||
fn skip(&self) -> bool {
|
||||
self.expectation == TestExpectation::Skip
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn error(&self) -> bool {
|
||||
self.expectation == TestExpectation::Error
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn check_initial_parse(
|
||||
&self,
|
||||
language: &Language,
|
||||
display_name: &str,
|
||||
check_output: bool,
|
||||
) -> bool {
|
||||
let mut log_session = None;
|
||||
let mut parser = get_parser(&mut log_session, "log.html");
|
||||
parser.set_language(language).unwrap();
|
||||
set_included_ranges(&mut parser, &self.input, self.template_delimiters);
|
||||
|
||||
let tree = parser.parse(&self.input, None).unwrap();
|
||||
|
||||
if !check_output {
|
||||
return true;
|
||||
}
|
||||
|
||||
let actual_output =
|
||||
render_test_output(&self.input, &tree, self.cst, self.has_fields).unwrap();
|
||||
|
||||
if actual_output == self.output {
|
||||
true
|
||||
} else {
|
||||
println!("Incorrect initial parse for {display_name}");
|
||||
DiffKey::print();
|
||||
println!("{}", TestDiff::new(&actual_output, &self.output));
|
||||
println!();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn flatten_tests(
|
||||
test: TestEntry,
|
||||
|
|
@ -382,20 +362,20 @@ pub fn flatten_tests(
|
|||
if !include.is_match(&name) {
|
||||
return;
|
||||
}
|
||||
} else if let Some(exclude) = exclude
|
||||
&& exclude.is_match(&name)
|
||||
{
|
||||
return;
|
||||
} else if let Some(exclude) = exclude {
|
||||
if exclude.is_match(&name) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
result.push(FlattenedTest {
|
||||
name,
|
||||
input,
|
||||
output,
|
||||
languages: attributes.languages,
|
||||
expectation: attributes.expectation,
|
||||
has_fields,
|
||||
cst: attributes.cst,
|
||||
languages: attributes.languages,
|
||||
error: attributes.error,
|
||||
skip: attributes.skip,
|
||||
template_delimiters: None,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,19 @@ use std::{
|
|||
collections::HashMap,
|
||||
os::raw::c_void,
|
||||
sync::{
|
||||
Mutex,
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
|
||||
Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
#[ctor::ctor]
|
||||
unsafe fn initialize_allocation_recording() {
|
||||
unsafe {
|
||||
tree_sitter::set_allocator(Some(tree_sitter::Allocator {
|
||||
malloc: ts_record_malloc,
|
||||
calloc: ts_record_calloc,
|
||||
realloc: ts_record_realloc,
|
||||
free: ts_record_free,
|
||||
}));
|
||||
}
|
||||
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)]
|
||||
|
|
@ -35,7 +33,7 @@ thread_local! {
|
|||
static RECORDER: AllocationRecorder = AllocationRecorder::default();
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
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;
|
||||
|
|
@ -105,11 +103,9 @@ fn record_dealloc(ptr: *mut c_void) {
|
|||
/// freed by calling `ts_record_free`.
|
||||
#[must_use]
|
||||
pub unsafe extern "C" fn ts_record_malloc(size: usize) -> *mut c_void {
|
||||
unsafe {
|
||||
let result = malloc(size);
|
||||
record_alloc(result);
|
||||
result
|
||||
}
|
||||
let result = malloc(size);
|
||||
record_alloc(result);
|
||||
result
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
|
|
@ -118,11 +114,9 @@ pub unsafe extern "C" fn ts_record_malloc(size: usize) -> *mut c_void {
|
|||
/// freed by calling `ts_record_free`.
|
||||
#[must_use]
|
||||
pub unsafe extern "C" fn ts_record_calloc(count: usize, size: usize) -> *mut c_void {
|
||||
unsafe {
|
||||
let result = calloc(count, size);
|
||||
record_alloc(result);
|
||||
result
|
||||
}
|
||||
let result = calloc(count, size);
|
||||
record_alloc(result);
|
||||
result
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
|
|
@ -131,16 +125,14 @@ pub unsafe extern "C" fn ts_record_calloc(count: usize, size: usize) -> *mut c_v
|
|||
/// freed by calling `ts_record_free`.
|
||||
#[must_use]
|
||||
pub unsafe extern "C" fn ts_record_realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
|
||||
unsafe {
|
||||
let result = realloc(ptr, size);
|
||||
if ptr.is_null() {
|
||||
record_alloc(result);
|
||||
} else if !core::ptr::eq(ptr, result) {
|
||||
record_dealloc(ptr);
|
||||
record_alloc(result);
|
||||
}
|
||||
result
|
||||
let result = realloc(ptr, size);
|
||||
if ptr.is_null() {
|
||||
record_alloc(result);
|
||||
} else if !core::ptr::eq(ptr, result) {
|
||||
record_dealloc(ptr);
|
||||
record_alloc(result);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
|
|
@ -148,8 +140,6 @@ pub unsafe extern "C" fn ts_record_realloc(ptr: *mut c_void, size: usize) -> *mu
|
|||
/// The caller must ensure that `ptr` was allocated by a previous call
|
||||
/// to `ts_record_malloc`, `ts_record_calloc`, or `ts_record_realloc`.
|
||||
pub unsafe extern "C" fn ts_record_free(ptr: *mut c_void) {
|
||||
unsafe {
|
||||
record_dealloc(ptr);
|
||||
free(ptr);
|
||||
}
|
||||
record_dealloc(ptr);
|
||||
free(ptr);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,10 @@
|
|||
use tree_sitter::{LogType, Node, Parser, Point, Range, Tree};
|
||||
|
||||
use super::{LOG_ENABLED, LOG_GRAPH_ENABLED, scope_sequence::ScopeSequence};
|
||||
use super::{scope_sequence::ScopeSequence, LOG_ENABLED, LOG_GRAPH_ENABLED};
|
||||
use crate::util;
|
||||
|
||||
struct SizeCheckFrame<'a> {
|
||||
node: Node<'a>,
|
||||
end_byte: usize,
|
||||
end_point: Point,
|
||||
child_count: u32,
|
||||
child_index: u32,
|
||||
last_child_end_byte: usize,
|
||||
last_child_end_point: Point,
|
||||
some_child_has_changes: bool,
|
||||
actual_named_child_count: usize,
|
||||
}
|
||||
|
||||
impl SizeCheckFrame<'_> {
|
||||
fn new<'a>(node: Node<'a>, line_offsets: &[usize]) -> SizeCheckFrame<'a> {
|
||||
pub fn check_consistent_sizes(tree: &Tree, input: &[u8]) {
|
||||
fn check(node: Node, line_offsets: &[usize]) {
|
||||
let start_byte = node.start_byte();
|
||||
let end_byte = node.end_byte();
|
||||
let start_point = node.start_position();
|
||||
|
|
@ -30,21 +18,37 @@ impl SizeCheckFrame<'_> {
|
|||
);
|
||||
assert_eq!(end_byte, line_offsets[end_point.row] + end_point.column);
|
||||
|
||||
SizeCheckFrame {
|
||||
node,
|
||||
end_byte,
|
||||
end_point,
|
||||
child_count: node.child_count(),
|
||||
child_index: 0,
|
||||
last_child_end_byte: start_byte,
|
||||
last_child_end_point: start_point,
|
||||
some_child_has_changes: false,
|
||||
actual_named_child_count: 0,
|
||||
let mut last_child_end_byte = start_byte;
|
||||
let mut last_child_end_point = start_point;
|
||||
let mut some_child_has_changes = false;
|
||||
let mut actual_named_child_count = 0;
|
||||
for i in 0..node.child_count() {
|
||||
let child = node.child(i as u32).unwrap();
|
||||
assert!(child.start_byte() >= last_child_end_byte);
|
||||
assert!(child.start_position() >= last_child_end_point);
|
||||
check(child, line_offsets);
|
||||
if child.has_changes() {
|
||||
some_child_has_changes = true;
|
||||
}
|
||||
if child.is_named() {
|
||||
actual_named_child_count += 1;
|
||||
}
|
||||
last_child_end_byte = child.end_byte();
|
||||
last_child_end_point = child.end_position();
|
||||
}
|
||||
|
||||
assert_eq!(actual_named_child_count, node.named_child_count());
|
||||
|
||||
if node.child_count() > 0 {
|
||||
assert!(end_byte >= last_child_end_byte);
|
||||
assert!(end_point >= last_child_end_point);
|
||||
}
|
||||
|
||||
if some_child_has_changes {
|
||||
assert!(node.has_changes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_consistent_sizes(tree: &Tree, input: &[u8]) {
|
||||
let mut line_offsets = vec![0];
|
||||
for (i, c) in input.iter().enumerate() {
|
||||
if *c == b'\n' {
|
||||
|
|
@ -52,41 +56,7 @@ pub fn check_consistent_sizes(tree: &Tree, input: &[u8]) {
|
|||
}
|
||||
}
|
||||
|
||||
let mut stack: Vec<SizeCheckFrame> = vec![SizeCheckFrame::new(tree.root_node(), &line_offsets)];
|
||||
while let Some(top) = stack.last_mut() {
|
||||
if top.child_index < top.child_count {
|
||||
let i = top.child_index;
|
||||
let child = top.node.child(i).unwrap();
|
||||
|
||||
assert!(child.start_byte() >= top.last_child_end_byte);
|
||||
assert!(child.start_position() >= top.last_child_end_point);
|
||||
if child.has_changes() {
|
||||
top.some_child_has_changes = true;
|
||||
}
|
||||
if child.is_named() {
|
||||
top.actual_named_child_count += 1;
|
||||
}
|
||||
top.last_child_end_byte = child.end_byte();
|
||||
top.last_child_end_point = child.end_position();
|
||||
top.child_index += 1;
|
||||
|
||||
stack.push(SizeCheckFrame::new(child, &line_offsets));
|
||||
continue;
|
||||
}
|
||||
|
||||
let frame = stack.pop().unwrap();
|
||||
assert_eq!(
|
||||
frame.actual_named_child_count,
|
||||
frame.node.named_child_count()
|
||||
);
|
||||
if frame.child_count > 0 {
|
||||
assert!(frame.end_byte >= frame.last_child_end_byte);
|
||||
assert!(frame.end_point >= frame.last_child_end_point);
|
||||
}
|
||||
if frame.some_child_has_changes {
|
||||
assert!(frame.node.has_changes());
|
||||
}
|
||||
}
|
||||
check(tree.root_node(), &line_offsets);
|
||||
}
|
||||
|
||||
pub fn check_changed_ranges(old_tree: &Tree, new_tree: &Tree, input: &[u8]) -> Result<(), String> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use rand::{RngExt, SeedableRng, distr::Alphanumeric, rngs::StdRng};
|
||||
use rand::{
|
||||
distributions::Alphanumeric,
|
||||
prelude::{Rng, SeedableRng, StdRng},
|
||||
};
|
||||
|
||||
const OPERATORS: &[char] = &[
|
||||
'+', '-', '<', '>', '(', ')', '*', '/', '&', '|', '!', ',', '.', '%',
|
||||
|
|
@ -13,7 +16,7 @@ impl Rand {
|
|||
}
|
||||
|
||||
pub fn unsigned(&mut self, max: usize) -> usize {
|
||||
self.0.random_range(0..=max)
|
||||
self.0.gen_range(0..=max)
|
||||
}
|
||||
|
||||
pub fn words(&mut self, max_count: usize) -> Vec<u8> {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use tree_sitter::{Point, Range, Tree};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ScopeSequence<'a>(Vec<ScopeStack<'a>>);
|
||||
pub struct ScopeSequence(Vec<ScopeStack>);
|
||||
|
||||
type ScopeStack<'a> = Vec<&'a str>;
|
||||
type ScopeStack = Vec<&'static str>;
|
||||
|
||||
impl<'a> ScopeSequence<'a> {
|
||||
impl ScopeSequence {
|
||||
#[must_use]
|
||||
pub fn new(tree: &'a Tree) -> Self {
|
||||
pub fn new(tree: &Tree) -> Self {
|
||||
let mut result = Self(Vec::new());
|
||||
let mut scope_stack = Vec::new();
|
||||
|
||||
|
|
|
|||
|
|
@ -4,18 +4,17 @@ use std::{
|
|||
fs,
|
||||
io::{self, Write as _},
|
||||
path::{self, Path, PathBuf},
|
||||
sync::{Arc, atomic::AtomicUsize},
|
||||
str,
|
||||
sync::{atomic::AtomicUsize, Arc},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use ansi_colours::{ansi256_from_rgb, rgb_from_ansi256};
|
||||
use anstyle::{Ansi256Color, AnsiColor, Color, Effects, RgbColor};
|
||||
use anyhow::Result;
|
||||
use clap::ValueEnum;
|
||||
use log::{info, warn};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeMap};
|
||||
use serde_json::{Value, json};
|
||||
use tree_sitter::ffi::{self, TSInputEncoding};
|
||||
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_json::{json, Value};
|
||||
use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter, HtmlRenderer};
|
||||
use tree_sitter_loader::Loader;
|
||||
|
||||
|
|
@ -26,9 +25,8 @@ pub const HTML_HEAD_HEADER: &str = "
|
|||
<style>
|
||||
body {
|
||||
font-family: monospace
|
||||
}";
|
||||
|
||||
pub const HTML_LINE_NUMBER_STYLE: &str = " .line-number {
|
||||
}
|
||||
.line-number {
|
||||
user-select: none;
|
||||
text-align: right;
|
||||
color: rgba(27,31,35,.3);
|
||||
|
|
@ -36,7 +34,8 @@ pub const HTML_LINE_NUMBER_STYLE: &str = " .line-number {
|
|||
}
|
||||
.line {
|
||||
white-space: pre;
|
||||
}";
|
||||
}
|
||||
</style>";
|
||||
|
||||
pub const HTML_BODY_HEADER: &str = "
|
||||
</head>
|
||||
|
|
@ -216,11 +215,11 @@ fn parse_style(style: &mut Style, json: Value) {
|
|||
style.css = None;
|
||||
}
|
||||
|
||||
if let Some(Color::Rgb(RgbColor(red, green, blue))) = style.ansi.get_fg_color()
|
||||
&& !terminal_supports_truecolor()
|
||||
{
|
||||
let ansi256 = Color::Ansi256(Ansi256Color(ansi256_from_rgb((red, green, blue))));
|
||||
style.ansi = style.ansi.fg_color(Some(ansi256));
|
||||
if let Some(Color::Rgb(RgbColor(red, green, blue))) = style.ansi.get_fg_color() {
|
||||
if !terminal_supports_truecolor() {
|
||||
let ansi256 = Color::Ansi256(Ansi256Color(ansi256_from_rgb((red, green, blue))));
|
||||
style.ansi = style.ansi.fg_color(Some(ansi256));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -308,40 +307,15 @@ fn terminal_supports_truecolor() -> bool {
|
|||
.is_ok_and(|truecolor| truecolor == "truecolor" || truecolor == "24bit")
|
||||
}
|
||||
|
||||
/// The kind of HTML emitted when highlighting to HTML.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
|
||||
pub enum HtmlOutput {
|
||||
/// A complete, self-contained document wrapping a plain
|
||||
/// `<div class="highlight"><pre><code>` block.
|
||||
Document,
|
||||
/// A complete document with a line-number column (a `<table>` layout).
|
||||
#[value(name = "line-numbers")]
|
||||
NumberedDocument,
|
||||
/// Only the code markup, without the surrounding document.
|
||||
Fragment,
|
||||
}
|
||||
|
||||
/// How token colors are applied in HTML output.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
|
||||
pub enum HtmlStyling {
|
||||
/// `class="..."` spans plus a generated `<style>` carrying the theme's colors.
|
||||
Classes,
|
||||
/// `style="..."` spans with the colors inlined.
|
||||
Inline,
|
||||
/// `class="..."` spans with no colors emitted (supply your own stylesheet).
|
||||
Minimal,
|
||||
}
|
||||
|
||||
pub struct HighlightOptions {
|
||||
pub theme: Theme,
|
||||
pub check: bool,
|
||||
pub captures_path: Option<PathBuf>,
|
||||
/// `None` for regular output, `Some((layout, style))` when emitting HTML.
|
||||
pub html: Option<(HtmlOutput, HtmlStyling)>,
|
||||
pub inline_styles: bool,
|
||||
pub html: bool,
|
||||
pub quiet: bool,
|
||||
pub print_time: bool,
|
||||
pub cancellation_flag: Arc<AtomicUsize>,
|
||||
pub encoding: Option<TSInputEncoding>,
|
||||
}
|
||||
|
||||
pub fn highlight(
|
||||
|
|
@ -384,60 +358,29 @@ pub fn highlight(
|
|||
}
|
||||
|
||||
let source = fs::read(path)?;
|
||||
|
||||
fn is_utf16_le_bom(bom_bytes: &[u8]) -> bool {
|
||||
bom_bytes == [0xFF, 0xFE]
|
||||
}
|
||||
|
||||
fn is_utf16_be_bom(bom_bytes: &[u8]) -> bool {
|
||||
bom_bytes == [0xFE, 0xFF]
|
||||
}
|
||||
|
||||
let encoding = match opts.encoding {
|
||||
None if source.len() >= 2 => {
|
||||
if is_utf16_le_bom(&source[0..2]) {
|
||||
Some(ffi::TSInputEncodingUTF16LE)
|
||||
} else if is_utf16_be_bom(&source[0..2]) {
|
||||
Some(ffi::TSInputEncodingUTF16BE)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => opts.encoding,
|
||||
};
|
||||
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
let time = Instant::now();
|
||||
let mut highlighter = Highlighter::new();
|
||||
let events = highlighter.highlight(
|
||||
config,
|
||||
&source,
|
||||
encoding,
|
||||
Some(&opts.cancellation_flag),
|
||||
|string| loader.highlight_config_for_injection_string(string),
|
||||
)?;
|
||||
let events =
|
||||
highlighter.highlight(config, &source, Some(&opts.cancellation_flag), |string| {
|
||||
loader.highlight_config_for_injection_string(string)
|
||||
})?;
|
||||
let theme = &opts.theme;
|
||||
|
||||
// A fragment is pure code markup, so it must not be prefixed with the filename.
|
||||
let html_fragment = opts
|
||||
.html
|
||||
.is_some_and(|(layout, _)| layout == HtmlOutput::Fragment);
|
||||
if !opts.quiet && print_name && !html_fragment {
|
||||
if !opts.quiet && print_name {
|
||||
writeln!(&mut stdout, "{name}")?;
|
||||
}
|
||||
|
||||
if let Some((layout, style)) = opts.html {
|
||||
if !opts.quiet && layout != HtmlOutput::Fragment {
|
||||
if opts.html {
|
||||
if !opts.quiet {
|
||||
writeln!(&mut stdout, "{HTML_HEAD_HEADER}")?;
|
||||
if layout == HtmlOutput::NumberedDocument {
|
||||
writeln!(&mut stdout, "{HTML_LINE_NUMBER_STYLE}")?;
|
||||
}
|
||||
if style == HtmlStyling::Classes {
|
||||
for (name, style) in theme.highlight_names.iter().zip(&theme.styles) {
|
||||
if let Some(css) = &style.css {
|
||||
writeln!(&mut stdout, " .{name} {{ {css}; }}")?;
|
||||
}
|
||||
writeln!(&mut stdout, " <style>")?;
|
||||
let names = theme.highlight_names.iter();
|
||||
let styles = theme.styles.iter();
|
||||
for (name, style) in names.zip(styles) {
|
||||
if let Some(css) = &style.css {
|
||||
writeln!(&mut stdout, " .{name} {{ {css}; }}")?;
|
||||
}
|
||||
}
|
||||
writeln!(&mut stdout, " </style>")?;
|
||||
|
|
@ -446,7 +389,7 @@ pub fn highlight(
|
|||
|
||||
let mut renderer = HtmlRenderer::new();
|
||||
renderer.render(events, &source, &move |highlight, output| {
|
||||
if style == HtmlStyling::Inline {
|
||||
if opts.inline_styles {
|
||||
output.extend(b"style='");
|
||||
output.extend(
|
||||
theme.styles[highlight.0]
|
||||
|
|
@ -454,6 +397,7 @@ pub fn highlight(
|
|||
.as_ref()
|
||||
.map_or_else(|| "".as_bytes(), |css_style| css_style.as_bytes()),
|
||||
);
|
||||
output.extend(b"'");
|
||||
} else {
|
||||
output.extend(b"class='");
|
||||
let mut parts = theme.highlight_names[highlight.0].split('.').peekable();
|
||||
|
|
@ -463,34 +407,21 @@ pub fn highlight(
|
|||
output.extend(b" ");
|
||||
}
|
||||
}
|
||||
output.extend(b"'");
|
||||
}
|
||||
output.extend(b"'");
|
||||
})?;
|
||||
|
||||
if !opts.quiet {
|
||||
if layout == HtmlOutput::NumberedDocument {
|
||||
writeln!(&mut stdout, "<table>")?;
|
||||
for (i, line) in renderer.lines().enumerate() {
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
"<tr><td class=line-number>{}</td><td class=line>{line}</td></tr>",
|
||||
i + 1,
|
||||
)?;
|
||||
}
|
||||
writeln!(&mut stdout, "</table>")?;
|
||||
} else {
|
||||
let mut body = renderer.lines().collect::<String>();
|
||||
if body.ends_with('\n') {
|
||||
body.pop();
|
||||
}
|
||||
writeln!(&mut stdout, "<table>")?;
|
||||
for (i, line) in renderer.lines().enumerate() {
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
"<div class=\"highlight\">\n<pre><code>{body}</code></pre>\n</div>",
|
||||
"<tr><td class=line-number>{}</td><td class=line>{line}</td></tr>",
|
||||
i + 1,
|
||||
)?;
|
||||
}
|
||||
if layout != HtmlOutput::Fragment {
|
||||
writeln!(&mut stdout, "{HTML_FOOTER}")?;
|
||||
}
|
||||
writeln!(&mut stdout, "</table>")?;
|
||||
writeln!(&mut stdout, "{HTML_FOOTER}")?;
|
||||
}
|
||||
} else {
|
||||
let mut style_stack = vec![theme.default_style().ansi];
|
||||
|
|
@ -537,7 +468,7 @@ mod tests {
|
|||
assert_eq!(style.css, None);
|
||||
|
||||
// darkcyan is an ANSI color and is preserved
|
||||
unsafe { env::set_var("COLORTERM", "") };
|
||||
env::set_var("COLORTERM", "");
|
||||
parse_style(&mut style, Value::String(DARK_CYAN.to_string()));
|
||||
assert_eq!(
|
||||
style.ansi.get_fg_color(),
|
||||
|
|
@ -546,7 +477,7 @@ mod tests {
|
|||
assert_eq!(style.css, Some("color: #00af87".to_string()));
|
||||
|
||||
// junglegreen is not an ANSI color and is preserved when the terminal supports it
|
||||
unsafe { env::set_var("COLORTERM", "truecolor") };
|
||||
env::set_var("COLORTERM", "truecolor");
|
||||
parse_style(&mut style, Value::String(JUNGLE_GREEN.to_string()));
|
||||
assert_eq!(
|
||||
style.ansi.get_fg_color(),
|
||||
|
|
@ -555,7 +486,7 @@ mod tests {
|
|||
assert_eq!(style.css, Some("color: #26a69a".to_string()));
|
||||
|
||||
// junglegreen gets approximated as cadetblue when the terminal does not support it
|
||||
unsafe { env::set_var("COLORTERM", "") };
|
||||
env::set_var("COLORTERM", "");
|
||||
parse_style(&mut style, Value::String(JUNGLE_GREEN.to_string()));
|
||||
assert_eq!(
|
||||
style.ansi.get_fg_color(),
|
||||
|
|
@ -564,9 +495,9 @@ mod tests {
|
|||
assert_eq!(style.css, Some("color: #26a69a".to_string()));
|
||||
|
||||
if let Ok(environment_variable) = original_environment_variable {
|
||||
unsafe { env::set_var("COLORTERM", environment_variable) };
|
||||
env::set_var("COLORTERM", environment_variable);
|
||||
} else {
|
||||
unsafe { env::remove_var("COLORTERM") };
|
||||
env::remove_var("COLORTERM");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -3,16 +3,15 @@ use std::{
|
|||
io::{Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
mpsc,
|
||||
mpsc, Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use glob::glob;
|
||||
|
||||
use crate::test::{TestEntry, parse_tests};
|
||||
use crate::test::{parse_tests, TestEntry};
|
||||
|
||||
pub enum CliInput {
|
||||
Paths(Vec<PathBuf>),
|
||||
|
|
@ -147,6 +146,7 @@ pub fn get_input(
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn get_test_info(
|
||||
test_entry: &TestEntry,
|
||||
target_test: u32,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
use std::io::Write;
|
||||
|
||||
use anstyle::{AnsiColor, Color, Style};
|
||||
use log::{Level, LevelFilter, Log, Metadata, Record};
|
||||
|
||||
use crate::paint::{Paint, RED, YELLOW};
|
||||
pub fn paint(color: Option<impl Into<Color>>, text: &str) -> String {
|
||||
let style = Style::new().fg_color(color.map(Into::into));
|
||||
format!("{style}{text}{style:#}")
|
||||
}
|
||||
|
||||
struct Logger;
|
||||
|
||||
|
|
@ -13,8 +17,16 @@ impl Log for Logger {
|
|||
|
||||
fn log(&self, record: &Record) {
|
||||
match record.level() {
|
||||
Level::Error => eprintln!("{} {}", Paint(RED, "Error:"), record.args()),
|
||||
Level::Warn => eprintln!("{} {}", Paint(YELLOW, "Warning:"), record.args()),
|
||||
Level::Error => eprintln!(
|
||||
"{} {}",
|
||||
paint(Some(AnsiColor::Red), "Error:"),
|
||||
record.args()
|
||||
),
|
||||
Level::Warn => eprintln!(
|
||||
"{} {}",
|
||||
paint(Some(AnsiColor::Yellow), "Warning:"),
|
||||
record.args()
|
||||
),
|
||||
Level::Info | Level::Debug => eprintln!("{}", record.args()),
|
||||
Level::Trace => eprintln!(
|
||||
"[{}] {}",
|
||||
|
|
|
|||
|
|
@ -5,24 +5,24 @@ use std::{
|
|||
};
|
||||
|
||||
use anstyle::{AnsiColor, Color, Style};
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use clap::{ArgGroup, Args, Command, FromArgMatches as _, Subcommand, ValueEnum, crate_authors};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use clap::{crate_authors, Args, Command, FromArgMatches as _, Subcommand, ValueEnum};
|
||||
use clap_complete::generate;
|
||||
use dialoguer::{Confirm, FuzzySelect, Input, MultiSelect, theme::ColorfulTheme};
|
||||
use dialoguer::{theme::ColorfulTheme, Confirm, FuzzySelect, Input, MultiSelect};
|
||||
use heck::ToUpperCamelCase;
|
||||
use log::{error, info, warn};
|
||||
use regex::Regex;
|
||||
use semver::Version as SemverVersion;
|
||||
use tree_sitter::{Parser, Point, ffi};
|
||||
use tree_sitter::{ffi, Parser, Point};
|
||||
use tree_sitter_cli::{
|
||||
fuzz::{
|
||||
DEFAULT_EDIT_COUNT, DEFAULT_ITERATION_COUNT, EDIT_COUNT, FuzzOptions, ITERATION_COUNT,
|
||||
LOG_ENABLED, LOG_GRAPH_ENABLED, START_SEED, fuzz_language_corpus,
|
||||
fuzz_language_corpus, FuzzOptions, DEFAULT_EDIT_COUNT, DEFAULT_ITERATION_COUNT, EDIT_COUNT,
|
||||
ITERATION_COUNT, LOG_ENABLED, LOG_GRAPH_ENABLED, START_SEED,
|
||||
},
|
||||
highlight::{self, HighlightOptions, HtmlOutput, HtmlStyling},
|
||||
init::{JsonConfigOpts, TREE_SITTER_JSON_SCHEMA, generate_grammar_files},
|
||||
input::{CliInput, get_input, get_tmp_source_file},
|
||||
logger, paint,
|
||||
highlight::{self, HighlightOptions},
|
||||
init::{generate_grammar_files, JsonConfigOpts, TREE_SITTER_JSON_SCHEMA},
|
||||
input::{get_input, get_tmp_source_file, CliInput},
|
||||
logger,
|
||||
parse::{self, ParseDebugType, ParseFileOptions, ParseOutput, ParseTheme},
|
||||
playground,
|
||||
query::{self, QueryFileOptions},
|
||||
|
|
@ -33,7 +33,7 @@ use tree_sitter_cli::{
|
|||
wasm,
|
||||
};
|
||||
use tree_sitter_config::Config;
|
||||
use tree_sitter_generate::{Diagnostic, GenerateError, OptLevel};
|
||||
use tree_sitter_generate::OptLevel;
|
||||
use tree_sitter_highlight::Highlighter;
|
||||
use tree_sitter_loader::{self as loader, Bindings, TreeSitterJSON};
|
||||
use tree_sitter_tags::TagsContext;
|
||||
|
|
@ -188,14 +188,10 @@ struct Build {
|
|||
/// Compile a parser in debug mode
|
||||
#[arg(long, short = '0')]
|
||||
pub debug: bool,
|
||||
/// Display verbose build information
|
||||
#[arg(short, long)]
|
||||
pub verbose: bool,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
#[command(alias = "p")]
|
||||
#[command(group(ArgGroup::new("graph_output").multiple(true)))]
|
||||
struct Parse {
|
||||
/// The path to a file with paths to source file(s)
|
||||
#[arg(long = "paths")]
|
||||
|
|
@ -211,29 +207,26 @@ struct Parse {
|
|||
pub lib_path: Option<PathBuf>,
|
||||
/// If `--lib-path` is used, the name of the language used to extract the
|
||||
/// library's language function
|
||||
#[arg(long, requires = "lib_path")]
|
||||
#[arg(long)]
|
||||
pub lang_name: Option<String>,
|
||||
/// Select a language by the scope instead of a file extension
|
||||
#[arg(long)]
|
||||
pub scope: Option<String>,
|
||||
/// Show parsing debug log
|
||||
#[arg(long, short = 'd')] // TODO: Rework once clap adds `default_missing_value_t`
|
||||
#[expect(
|
||||
clippy::option_option,
|
||||
reason = "required by clap for optional flag with optional value"
|
||||
)]
|
||||
#[allow(clippy::option_option)]
|
||||
pub debug: Option<Option<ParseDebugType>>,
|
||||
/// Compile a parser in debug mode
|
||||
#[arg(long, short = '0')]
|
||||
pub debug_build: bool,
|
||||
/// Produce the log.html file with debug graphs
|
||||
#[arg(long, short = 'D', group = "graph_output")]
|
||||
#[arg(long, short = 'D')]
|
||||
pub debug_graph: bool,
|
||||
/// Compile parsers to Wasm instead of native dynamic libraries
|
||||
#[arg(long, hide = cfg!(not(feature = "wasm")))]
|
||||
pub wasm: bool,
|
||||
/// Output the parse data with graphviz dot
|
||||
#[arg(long = "dot", group = "graph_output")]
|
||||
#[arg(long = "dot")]
|
||||
pub output_dot: bool,
|
||||
/// Output the parse data in XML format
|
||||
#[arg(long = "xml", short = 'x')]
|
||||
|
|
@ -253,10 +246,7 @@ struct Parse {
|
|||
/// Suppress main output
|
||||
#[arg(long, short)]
|
||||
pub quiet: bool,
|
||||
#[expect(
|
||||
clippy::doc_markdown,
|
||||
reason = "doc string contains format syntax, not code identifiers"
|
||||
)]
|
||||
#[allow(clippy::doc_markdown)]
|
||||
/// Apply edits in the format: \"row,col|position delcount insert_text\", can be supplied
|
||||
/// multiple times
|
||||
#[arg(
|
||||
|
|
@ -267,8 +257,8 @@ struct Parse {
|
|||
/// The encoding of the input files
|
||||
#[arg(long)]
|
||||
pub encoding: Option<Encoding>,
|
||||
/// Open `log.html` in the default browser, if `--debug-graph` or `--dot` is supplied
|
||||
#[arg(long, requires = "graph_output")]
|
||||
/// Open `log.html` in the default browser, if `--debug-graph` is supplied
|
||||
#[arg(long)]
|
||||
pub open_log: bool,
|
||||
/// Deprecated: use --json-summary
|
||||
#[arg(long, conflicts_with = "json_summary", conflicts_with = "stat")]
|
||||
|
|
@ -293,9 +283,9 @@ struct Parse {
|
|||
|
||||
#[derive(ValueEnum, Clone)]
|
||||
pub enum Encoding {
|
||||
Utf8 = 0,
|
||||
Utf16LE = 1,
|
||||
Utf16BE = 2,
|
||||
Utf8,
|
||||
Utf16LE,
|
||||
Utf16BE,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
@ -318,7 +308,7 @@ struct Test {
|
|||
pub lib_path: Option<PathBuf>,
|
||||
/// If `--lib-path` is used, the name of the language used to extract the
|
||||
/// library's language function
|
||||
#[arg(long, requires = "lib_path")]
|
||||
#[arg(long)]
|
||||
pub lang_name: Option<String>,
|
||||
/// Update all syntax trees in corpus files with current parser output
|
||||
#[arg(long, short)]
|
||||
|
|
@ -336,7 +326,7 @@ struct Test {
|
|||
#[arg(long, hide = cfg!(not(feature = "wasm")))]
|
||||
pub wasm: bool,
|
||||
/// Open `log.html` in the default browser, if `--debug-graph` is supplied
|
||||
#[arg(long, requires = "debug_graph")]
|
||||
#[arg(long)]
|
||||
pub open_log: bool,
|
||||
/// The path to an alternative config.json file
|
||||
#[arg(long)]
|
||||
|
|
@ -344,9 +334,6 @@ struct Test {
|
|||
/// Force showing fields in test diffs
|
||||
#[arg(long)]
|
||||
pub show_fields: bool,
|
||||
/// Force showing '+' and '-' in test diffs
|
||||
#[arg(long)]
|
||||
pub show_diff_markers: bool,
|
||||
/// Show parsing statistics
|
||||
#[arg(long)]
|
||||
pub stat: Option<TestStats>,
|
||||
|
|
@ -363,10 +350,6 @@ struct Test {
|
|||
|
||||
#[derive(Args)]
|
||||
#[command(alias = "publish")]
|
||||
#[expect(
|
||||
clippy::struct_field_names,
|
||||
reason = "field names map to CLI arguments"
|
||||
)]
|
||||
/// Display or increment the version of a grammar
|
||||
struct Version {
|
||||
/// The version to bump to
|
||||
|
|
@ -406,7 +389,7 @@ struct Fuzz {
|
|||
pub lib_path: Option<PathBuf>,
|
||||
/// If `--lib-path` is used, the name of the language used to extract the
|
||||
/// library's language function
|
||||
#[arg(long, requires = "lib_path")]
|
||||
#[arg(long)]
|
||||
pub lang_name: Option<String>,
|
||||
#[arg(
|
||||
long,
|
||||
|
|
@ -437,10 +420,6 @@ struct Fuzz {
|
|||
|
||||
#[derive(Args)]
|
||||
#[command(alias = "q")]
|
||||
#[expect(
|
||||
clippy::struct_field_names,
|
||||
reason = "field names map to CLI arguments"
|
||||
)]
|
||||
struct Query {
|
||||
/// Path to a file with queries
|
||||
#[arg(index = 1, required = true)]
|
||||
|
|
@ -453,7 +432,7 @@ struct Query {
|
|||
pub lib_path: Option<PathBuf>,
|
||||
/// If `--lib-path` is used, the name of the language used to extract the
|
||||
/// library's language function
|
||||
#[arg(long, requires = "lib_path")]
|
||||
#[arg(long)]
|
||||
pub lang_name: Option<String>,
|
||||
/// Measure execution time
|
||||
#[arg(long, short)]
|
||||
|
|
@ -508,20 +487,14 @@ struct Highlight {
|
|||
/// Generate highlighting as an HTML document
|
||||
#[arg(long, short = 'H')]
|
||||
pub html: bool,
|
||||
/// Deprecated: use `--style classes`
|
||||
#[arg(long, requires = "html", conflicts_with = "style")]
|
||||
/// When generating HTML, use css classes rather than inline styles
|
||||
#[arg(long)]
|
||||
pub css_classes: bool,
|
||||
/// When generating HTML, the document structure to emit
|
||||
#[arg(long, requires = "html", value_enum, default_value = "document")]
|
||||
pub layout: HtmlOutput,
|
||||
/// When generating HTML, how token colors are applied
|
||||
#[arg(long, requires = "html", value_enum, default_value = "classes")]
|
||||
pub style: HtmlStyling,
|
||||
/// Check that highlighting captures conform strictly to standards
|
||||
#[arg(long)]
|
||||
pub check: bool,
|
||||
/// The path to a file with captures
|
||||
#[arg(long, requires = "check")]
|
||||
#[arg(long)]
|
||||
pub captures_path: Option<PathBuf>,
|
||||
/// The paths to files with queries
|
||||
#[arg(long, num_args = 1..)]
|
||||
|
|
@ -554,9 +527,6 @@ struct Highlight {
|
|||
/// Force rebuild the parser
|
||||
#[arg(short, long)]
|
||||
pub rebuild: bool,
|
||||
/// The encoding of the input files
|
||||
#[arg(long)]
|
||||
pub encoding: Option<Encoding>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
@ -819,7 +789,7 @@ impl Init {
|
|||
|
||||
let enabled = MultiSelect::new()
|
||||
.with_prompt("Bindings")
|
||||
.items_checked(languages.iter().copied())
|
||||
.items_checked(&languages)
|
||||
.interact()?
|
||||
.into_iter()
|
||||
.map(|i| languages[i].0);
|
||||
|
|
@ -893,7 +863,7 @@ impl Init {
|
|||
|
||||
let idx = FuzzySelect::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Which field would you like to change?")
|
||||
.items(choices)
|
||||
.items(&choices)
|
||||
.interact()?;
|
||||
|
||||
set_choice!(choices[idx]);
|
||||
|
|
@ -911,7 +881,7 @@ impl Init {
|
|||
|
||||
let new_config = format!("{}\n", serde_json::to_string_pretty(&json)?);
|
||||
// Write the re-serialized config back, as newly added optional boolean fields
|
||||
// will be included with explicit `false`s rather than implicit `null`s
|
||||
// will be included with explicit `false`s rather than implict `null`s
|
||||
if self.update && !old_config.trim().eq(new_config.trim()) {
|
||||
info!("Updating tree-sitter.json");
|
||||
fs::write(
|
||||
|
|
@ -958,8 +928,7 @@ impl Generate {
|
|||
self.json_summary
|
||||
};
|
||||
|
||||
let mut diagnostics = Vec::new();
|
||||
let result = tree_sitter_generate::generate_parser_in_directory(
|
||||
if let Err(err) = tree_sitter_generate::generate_parser_in_directory(
|
||||
current_dir,
|
||||
self.output.as_deref(),
|
||||
self.grammar_path.as_deref(),
|
||||
|
|
@ -972,33 +941,16 @@ impl Generate {
|
|||
} else {
|
||||
OptLevel::default()
|
||||
},
|
||||
&mut diagnostics,
|
||||
);
|
||||
if json_summary {
|
||||
#[derive(serde::Serialize)]
|
||||
struct Envelope<'a> {
|
||||
diagnostics: &'a [Diagnostic],
|
||||
error: Option<&'a GenerateError>,
|
||||
}
|
||||
let envelope = Envelope {
|
||||
diagnostics: &diagnostics,
|
||||
error: result.as_ref().err(),
|
||||
};
|
||||
eprintln!("{}", serde_json::to_string_pretty(&envelope)?);
|
||||
if result.is_err() {
|
||||
) {
|
||||
if json_summary {
|
||||
eprintln!("{}", serde_json::to_string_pretty(&err)?);
|
||||
// Exit early to prevent errors from being printed a second time in the caller
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else {
|
||||
for d in &diagnostics {
|
||||
warn!("{d}");
|
||||
}
|
||||
if let Err(err) = result {
|
||||
} else {
|
||||
// Removes extra context associated with the error
|
||||
Err(anyhow!(err.to_string())).with_context(|| "Error when generating parser")?;
|
||||
}
|
||||
}
|
||||
|
||||
if self.build {
|
||||
warn!("--build is deprecated, use the `build` command");
|
||||
if let Some(path) = self.libdir {
|
||||
|
|
@ -1016,7 +968,6 @@ impl Build {
|
|||
let grammar_path = current_dir.join(self.path.unwrap_or_default());
|
||||
|
||||
loader.debug_build(self.debug);
|
||||
loader.verbose_build(self.verbose);
|
||||
|
||||
if self.wasm {
|
||||
let output_path = self.output.map(|path| current_dir.join(path));
|
||||
|
|
@ -1034,7 +985,7 @@ impl Build {
|
|||
.context("Output path must have a parent")?;
|
||||
let name = full_path
|
||||
.file_name()
|
||||
.context("Output path must have a filename")?;
|
||||
.context("Ouput path must have a filename")?;
|
||||
fs::create_dir_all(parent_path).context("Failed to create output path")?;
|
||||
let mut canon_path = parent_path.canonicalize().context("Invalid output path")?;
|
||||
canon_path.push(name);
|
||||
|
|
@ -1072,6 +1023,7 @@ impl Build {
|
|||
impl Parse {
|
||||
fn run(self, mut loader: loader::Loader, current_dir: &Path) -> Result<()> {
|
||||
let config = Config::load(self.config_path)?;
|
||||
let color = env::var("NO_COLOR").map_or(true, |v| v != "1");
|
||||
let json_summary = if self.json {
|
||||
warn!("--json is deprecated, use --json-summary instead");
|
||||
true
|
||||
|
|
@ -1090,7 +1042,7 @@ impl Parse {
|
|||
ParseOutput::Normal
|
||||
};
|
||||
|
||||
let parse_theme = if paint::color_enabled() {
|
||||
let parse_theme = if color {
|
||||
config
|
||||
.get::<parse::Config>()
|
||||
.with_context(|| "Failed to parse CST theme")?
|
||||
|
|
@ -1128,6 +1080,9 @@ impl Parse {
|
|||
let timeout = self.timeout.unwrap_or_default();
|
||||
|
||||
let mut has_error = false;
|
||||
let loader_config = config.get()?;
|
||||
loader.find_all_languages(&loader_config)?;
|
||||
|
||||
let should_track_stats = self.stat;
|
||||
let mut stats = parse::ParseStats::default();
|
||||
let debug: ParseDebugType = match self.debug {
|
||||
|
|
@ -1170,11 +1125,10 @@ impl Parse {
|
|||
has_error |= !parse_result.successful;
|
||||
};
|
||||
|
||||
let lib_info = get_lib_info(self.lib_path.as_ref(), self.lang_name.as_ref(), current_dir);
|
||||
if lib_info.is_none() {
|
||||
let loader_config = config.get()?;
|
||||
loader.find_all_languages(&loader_config)?;
|
||||
if self.lib_path.is_none() && self.lang_name.is_some() {
|
||||
warn!("--lang-name` specified without --lib-path. This argument will be ignored.");
|
||||
}
|
||||
let lib_info = get_lib_info(self.lib_path.as_ref(), self.lang_name.as_ref(), current_dir);
|
||||
|
||||
let input = get_input(
|
||||
self.paths_file.as_deref(),
|
||||
|
|
@ -1325,6 +1279,7 @@ fn check_test(
|
|||
impl Test {
|
||||
fn run(self, mut loader: loader::Loader, current_dir: &Path) -> Result<()> {
|
||||
let config = Config::load(self.config_path)?;
|
||||
let color = env::var("NO_COLOR").map_or(true, |v| v != "1");
|
||||
let stat = self.stat.unwrap_or_default();
|
||||
|
||||
loader.debug_build(self.debug_build);
|
||||
|
|
@ -1342,6 +1297,9 @@ impl Test {
|
|||
});
|
||||
}
|
||||
|
||||
if self.lib_path.is_none() && self.lang_name.is_some() {
|
||||
warn!("--lang-name` specified without --lib-path. This argument will be ignored.");
|
||||
}
|
||||
let languages = loader.languages_at_path(current_dir)?;
|
||||
let language = if let Some(ref lib_path) = self.lib_path {
|
||||
let lib_info =
|
||||
|
|
@ -1363,9 +1321,13 @@ impl Test {
|
|||
parser.set_language(language)?;
|
||||
|
||||
let test_dir = current_dir.join("test");
|
||||
let mut test_summary =
|
||||
TestSummary::new(stat, self.update, self.overview_only, self.json_summary);
|
||||
test_summary.use_markers = self.show_diff_markers;
|
||||
let mut test_summary = TestSummary::new(
|
||||
color,
|
||||
stat,
|
||||
self.update,
|
||||
self.overview_only,
|
||||
self.json_summary,
|
||||
);
|
||||
|
||||
// Run the corpus tests. Look for them in `test/corpus`.
|
||||
let test_corpus_dir = test_dir.join("corpus");
|
||||
|
|
@ -1380,6 +1342,7 @@ impl Test {
|
|||
update: self.update,
|
||||
open_log: self.open_log,
|
||||
languages: languages.iter().map(|(l, n)| (n.as_str(), l)).collect(),
|
||||
color,
|
||||
show_fields: self.show_fields,
|
||||
overview_only: self.overview_only,
|
||||
};
|
||||
|
|
@ -1446,7 +1409,7 @@ impl Test {
|
|||
// For the rest of the queries, find their tests and run them
|
||||
for entry in walkdir::WalkDir::new(&query_dir)
|
||||
.into_iter()
|
||||
.filter_map(std::result::Result::ok)
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.file_type().is_file())
|
||||
{
|
||||
let stem = entry
|
||||
|
|
@ -1517,6 +1480,9 @@ impl Fuzz {
|
|||
loader.sanitize_build(true);
|
||||
loader.force_rebuild(self.rebuild || self.grammar_path.is_some());
|
||||
|
||||
if self.lib_path.is_none() && self.lang_name.is_some() {
|
||||
warn!("--lang-name` specified without --lib-path. This argument will be ignored.");
|
||||
}
|
||||
let languages = loader.languages_at_path(current_dir)?;
|
||||
let (language, language_name) = if let Some(ref lib_path) = self.lib_path {
|
||||
let lib_info = get_lib_info(Some(lib_path), self.lang_name.as_ref(), current_dir)
|
||||
|
|
@ -1564,23 +1530,24 @@ impl Fuzz {
|
|||
impl Query {
|
||||
fn run(self, mut loader: loader::Loader, current_dir: &Path) -> Result<()> {
|
||||
let config = Config::load(self.config_path)?;
|
||||
let lib_info = get_lib_info(self.lib_path.as_ref(), self.lang_name.as_ref(), current_dir);
|
||||
if lib_info.is_none() {
|
||||
let loader_config = config.get()?;
|
||||
loader.find_all_languages(&loader_config)?;
|
||||
}
|
||||
let loader_config = config.get()?;
|
||||
loader.force_rebuild(self.rebuild || self.grammar_path.is_some());
|
||||
loader.find_all_languages(&loader_config)?;
|
||||
let query_path = Path::new(&self.query_path);
|
||||
|
||||
let byte_range = parse_range(self.byte_range.as_deref(), |x| x)?;
|
||||
let point_range = parse_range(self.row_range.as_deref(), |row| Point::new(row, 0))?;
|
||||
let containing_byte_range = parse_range(self.containing_byte_range.as_deref(), |x| x)?;
|
||||
let containing_point_range = parse_range(self.containing_row_range.as_deref(), |row| {
|
||||
Point::new(row, 0)
|
||||
})?;
|
||||
let byte_range = parse_range(&self.byte_range, |x| x)?;
|
||||
let point_range = parse_range(&self.row_range, |row| Point::new(row, 0))?;
|
||||
let containing_byte_range = parse_range(&self.containing_byte_range, |x| x)?;
|
||||
let containing_point_range =
|
||||
parse_range(&self.containing_row_range, |row| Point::new(row, 0))?;
|
||||
|
||||
let cancellation_flag = util::cancel_on_signal();
|
||||
|
||||
if self.lib_path.is_none() && self.lang_name.is_some() {
|
||||
warn!("--lang-name specified without --lib-path. This argument will be ignored.");
|
||||
}
|
||||
let lib_info = get_lib_info(self.lib_path.as_ref(), self.lang_name.as_ref(), current_dir);
|
||||
|
||||
let input = get_input(
|
||||
self.paths_file.as_deref(),
|
||||
self.paths,
|
||||
|
|
@ -1704,29 +1671,15 @@ impl Highlight {
|
|||
}
|
||||
}
|
||||
|
||||
let encoding = self.encoding.map(|e| match e {
|
||||
Encoding::Utf8 => ffi::TSInputEncodingUTF8,
|
||||
Encoding::Utf16LE => ffi::TSInputEncodingUTF16LE,
|
||||
Encoding::Utf16BE => ffi::TSInputEncodingUTF16BE,
|
||||
});
|
||||
|
||||
let style = if self.css_classes {
|
||||
// TODO: Remove during the 0.28 release cycle
|
||||
warn!("--css-classes is deprecated, use --style classes instead");
|
||||
HtmlStyling::Classes
|
||||
} else {
|
||||
self.style
|
||||
};
|
||||
|
||||
let options = HighlightOptions {
|
||||
theme: theme_config.theme,
|
||||
check: self.check,
|
||||
captures_path: self.captures_path,
|
||||
html: self.html.then_some((self.layout, style)),
|
||||
inline_styles: !self.css_classes,
|
||||
html: self.html,
|
||||
quiet: self.quiet,
|
||||
print_time: self.time,
|
||||
cancellation_flag: cancellation_flag.clone(),
|
||||
encoding,
|
||||
};
|
||||
|
||||
let input = get_input(
|
||||
|
|
@ -1811,7 +1764,7 @@ impl Highlight {
|
|||
let path = get_tmp_source_file(&contents)?;
|
||||
|
||||
let (language, language_config) =
|
||||
if let (Some(l), Some(lc)) = (language, language_configuration) {
|
||||
if let (Some(l), Some(lc)) = (language.clone(), language_configuration) {
|
||||
(l, lc)
|
||||
} else {
|
||||
let language = languages
|
||||
|
|
@ -1953,7 +1906,7 @@ impl Tags {
|
|||
let path = get_tmp_source_file(&contents)?;
|
||||
|
||||
let (language, language_config) =
|
||||
if let (Some(l), Some(lc)) = (language, language_configuration) {
|
||||
if let (Some(l), Some(lc)) = (language.clone(), language_configuration) {
|
||||
(l, lc)
|
||||
} else {
|
||||
let languages = loader.languages_at_path(current_dir)?;
|
||||
|
|
@ -2048,10 +2001,10 @@ fn main() {
|
|||
let result = run();
|
||||
if let Err(err) = &result {
|
||||
// Ignore BrokenPipe errors
|
||||
if let Some(error) = err.downcast_ref::<std::io::Error>()
|
||||
&& error.kind() == std::io::ErrorKind::BrokenPipe
|
||||
{
|
||||
return;
|
||||
if let Some(error) = err.downcast_ref::<std::io::Error>() {
|
||||
if error.kind() == std::io::ErrorKind::BrokenPipe {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if !err.to_string().is_empty() {
|
||||
error!("{err:?}");
|
||||
|
|
@ -2104,7 +2057,7 @@ fn run() -> Result<()> {
|
|||
| Commands::Complete(_) => &None,
|
||||
}
|
||||
.as_ref()
|
||||
.map_or_else(|| env::current_dir().unwrap(), std::clone::Clone::clone);
|
||||
.map_or_else(|| env::current_dir().unwrap(), |p| p.clone());
|
||||
|
||||
let loader = loader::Loader::new()?;
|
||||
|
||||
|
|
@ -2176,7 +2129,7 @@ fn get_lib_info<'a>(
|
|||
// Use the user-specified name if present, otherwise try to derive it from
|
||||
// the lib path
|
||||
match (
|
||||
language_name.map(std::string::String::as_str),
|
||||
language_name.map(|s| s.as_str()),
|
||||
lib_path.file_stem().and_then(|s| s.to_str()),
|
||||
) {
|
||||
(Some(name), _) | (None, Some(name)) => Some((absolute_lib_path, name)),
|
||||
|
|
@ -2189,10 +2142,10 @@ fn get_lib_info<'a>(
|
|||
|
||||
/// Parse a range string of the form "start:end" into an optional Range<T>.
|
||||
fn parse_range<T>(
|
||||
range_str: Option<&str>,
|
||||
range_str: &Option<String>,
|
||||
make: impl Fn(usize) -> T,
|
||||
) -> Result<Option<std::ops::Range<T>>> {
|
||||
if let Some(range) = range_str {
|
||||
if let Some(range) = range_str.as_ref() {
|
||||
let err_msg = format!("Invalid range '{range}', expected 'start:end'");
|
||||
let mut parts = range.split(':');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
use anstyle::{AnsiColor, Color, Style};
|
||||
|
||||
pub const RED: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Red)));
|
||||
pub const YELLOW: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow)));
|
||||
|
||||
/// Wraps a `Display` value with a style; emits ANSI codes only when
|
||||
/// [`color_enabled`] is true.
|
||||
pub struct Paint<T>(pub Style, pub T);
|
||||
|
||||
pub fn color_enabled() -> bool {
|
||||
static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
|
||||
*ENABLED.get_or_init(|| std::env::var_os("NO_COLOR").is_none_or(|v| v.is_empty()))
|
||||
}
|
||||
|
||||
pub fn paint<T>(color: Option<impl Into<Color>>, text: T) -> Paint<T> {
|
||||
Paint(Style::new().fg_color(color.map(Into::into)), text)
|
||||
}
|
||||
|
||||
impl<T: std::fmt::Display> std::fmt::Display for Paint<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if color_enabled() {
|
||||
write!(f, "{}{}{:#}", self.0, self.1, self.0)
|
||||
} else {
|
||||
self.1.fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,17 +8,17 @@ use std::{
|
|||
};
|
||||
|
||||
use anstyle::{AnsiColor, Color, RgbColor};
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use clap::ValueEnum;
|
||||
use log::info;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::{
|
||||
InputEdit, Language, LogType, ParseOptions, ParseState, Parser, Point, Range, Tree, TreeCursor,
|
||||
ffi,
|
||||
ffi, InputEdit, Language, LogType, ParseOptions, ParseState, Parser, Point, Range, Tree,
|
||||
TreeCursor,
|
||||
};
|
||||
|
||||
use crate::{fuzz::edits::Edit, paint::paint, util};
|
||||
use crate::{fuzz::edits::Edit, logger::paint, util};
|
||||
|
||||
#[derive(Debug, Default, Serialize, JsonSchema)]
|
||||
pub struct Stats {
|
||||
|
|
@ -286,10 +286,6 @@ pub fn parse_file_at_path(
|
|||
max_path_length: usize,
|
||||
opts: &mut ParseFileOptions,
|
||||
) -> Result<()> {
|
||||
#[expect(
|
||||
clippy::collection_is_never_read,
|
||||
reason = "value is held for its Drop side effect"
|
||||
)]
|
||||
let mut _log_session = None;
|
||||
parser.set_language(language)?;
|
||||
let mut source_code = fs::read(path).with_context(|| format!("Error reading {name:?}"))?;
|
||||
|
|
@ -301,6 +297,7 @@ pub fn parse_file_at_path(
|
|||
// Log to stderr if `--debug` was passed
|
||||
else if opts.debug != ParseDebugType::Quiet {
|
||||
let mut curr_version: usize = 0;
|
||||
let use_color = std::env::var("NO_COLOR").map_or(true, |v| v != "1");
|
||||
let debug = opts.debug;
|
||||
parser.set_logger(Some(Box::new(move |log_type, message| {
|
||||
if debug == ParseDebugType::Normal {
|
||||
|
|
@ -323,21 +320,30 @@ pub fn parse_file_at_path(
|
|||
.parse()
|
||||
.unwrap();
|
||||
}
|
||||
let color = Some(colors[curr_version % colors.len()]);
|
||||
let prefix = if log_type == LogType::Lex { " " } else { "" };
|
||||
writeln!(&mut io::stderr(), "{prefix}{}", paint(color, message)).unwrap();
|
||||
let color = if use_color {
|
||||
Some(colors[curr_version % colors.len()])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut out = if log_type == LogType::Lex {
|
||||
" ".to_string()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
out += &paint(color, message);
|
||||
writeln!(&mut io::stderr(), "{out}").unwrap();
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
let parse_time = Instant::now();
|
||||
|
||||
#[inline]
|
||||
#[inline(always)]
|
||||
fn is_utf16_le_bom(bom_bytes: &[u8]) -> bool {
|
||||
bom_bytes == [0xFF, 0xFE]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[inline(always)]
|
||||
fn is_utf16_be_bom(bom_bytes: &[u8]) -> bool {
|
||||
bom_bytes == [0xFE, 0xFF]
|
||||
}
|
||||
|
|
@ -362,13 +368,13 @@ pub fn parse_file_at_path(
|
|||
// after the specified number of microseconds.
|
||||
let start_time = Instant::now();
|
||||
let progress_callback = &mut |_: &ParseState| {
|
||||
if let Some(cancellation_flag) = opts.cancellation_flag
|
||||
&& cancellation_flag.load(Ordering::SeqCst) != 0
|
||||
{
|
||||
return ControlFlow::Break(());
|
||||
if let Some(cancellation_flag) = opts.cancellation_flag {
|
||||
if cancellation_flag.load(Ordering::SeqCst) != 0 {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
}
|
||||
|
||||
if opts.timeout > 0 && start_time.elapsed().as_micros() > u128::from(opts.timeout) {
|
||||
if opts.timeout > 0 && start_time.elapsed().as_micros() > opts.timeout as u128 {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
|
||||
|
|
@ -380,10 +386,8 @@ pub fn parse_file_at_path(
|
|||
let tree = match encoding {
|
||||
Some(encoding) if encoding == ffi::TSInputEncodingUTF16LE => {
|
||||
let source_code_utf16 = source_code
|
||||
.as_chunks::<2>()
|
||||
.0
|
||||
.iter()
|
||||
.map(|&chunk| u16::from_le_bytes(chunk))
|
||||
.chunks_exact(2)
|
||||
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
|
||||
.collect::<Vec<_>>();
|
||||
parser.parse_utf16_le_with_options(
|
||||
&mut |i, _| {
|
||||
|
|
@ -399,10 +403,8 @@ pub fn parse_file_at_path(
|
|||
}
|
||||
Some(encoding) if encoding == ffi::TSInputEncodingUTF16BE => {
|
||||
let source_code_utf16 = source_code
|
||||
.as_chunks::<2>()
|
||||
.0
|
||||
.iter()
|
||||
.map(|&chunk| u16::from_be_bytes(chunk))
|
||||
.chunks_exact(2)
|
||||
.map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]]))
|
||||
.collect::<Vec<_>>();
|
||||
parser.parse_utf16_be_with_options(
|
||||
&mut |i, _| {
|
||||
|
|
@ -542,10 +544,10 @@ pub fn parse_file_at_path(
|
|||
}
|
||||
write!(&mut stdout, "</{}>", tag.expect("there is a tag"))?;
|
||||
// we only write a line in the case where it's the last sibling
|
||||
if let Some(parent) = node.parent()
|
||||
&& parent.child(parent.child_count() - 1).unwrap() == node
|
||||
{
|
||||
stdout.write_all(b"\n")?;
|
||||
if let Some(parent) = node.parent() {
|
||||
if parent.child(parent.child_count() as u32 - 1).unwrap() == node {
|
||||
stdout.write_all(b"\n")?;
|
||||
}
|
||||
}
|
||||
needs_newline = true;
|
||||
}
|
||||
|
|
@ -774,7 +776,7 @@ pub fn render_cst<'a, 'b: 'a>(
|
|||
cursor: &mut TreeCursor<'a>,
|
||||
opts: &ParseFileOptions,
|
||||
out: &mut impl Write,
|
||||
) -> io::Result<()> {
|
||||
) -> Result<()> {
|
||||
let lossy_source_code = String::from_utf8_lossy(source_code);
|
||||
let total_width = lossy_source_code
|
||||
.lines()
|
||||
|
|
@ -827,19 +829,19 @@ pub fn render_cst<'a, 'b: 'a>(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
struct CstNodeText<'a>(&'a str);
|
||||
|
||||
impl std::fmt::Display for CstNodeText<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use std::fmt::Write as _;
|
||||
for c in self.0.chars() {
|
||||
match escape_invisible(c).or_else(|| escape_delimiter(c)) {
|
||||
Some(esc) => f.write_str(esc)?,
|
||||
None => f.write_char(c)?,
|
||||
fn render_node_text(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 if let Some(esc) = escape_delimiter(c) {
|
||||
acc.push_str(esc);
|
||||
} else {
|
||||
acc.push(c);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
acc
|
||||
})
|
||||
}
|
||||
|
||||
fn write_node_text(
|
||||
|
|
@ -850,21 +852,21 @@ fn write_node_text(
|
|||
source: &str,
|
||||
color: Option<impl Into<Color> + Copy>,
|
||||
text_info: (usize, usize),
|
||||
) -> io::Result<()> {
|
||||
) -> Result<()> {
|
||||
let (total_width, indent_level) = text_info;
|
||||
let (quote, quote_color) = if is_named {
|
||||
('`', opts.parse_theme.backtick)
|
||||
} else {
|
||||
('\"', color.map(std::convert::Into::into))
|
||||
('\"', color.map(|c| c.into()))
|
||||
};
|
||||
|
||||
if !is_named {
|
||||
write!(
|
||||
out,
|
||||
"{}{}{}",
|
||||
paint(quote_color, quote),
|
||||
paint(color, CstNodeText(source)),
|
||||
paint(quote_color, quote),
|
||||
paint(quote_color, &String::from(quote)),
|
||||
paint(color, &render_node_text(source)),
|
||||
paint(quote_color, &String::from(quote)),
|
||||
)?;
|
||||
} else {
|
||||
let multiline = source.contains('\n');
|
||||
|
|
@ -883,34 +885,24 @@ fn write_node_text(
|
|||
} else {
|
||||
0
|
||||
};
|
||||
if multiline {
|
||||
writeln!(out)?;
|
||||
if !opts.no_ranges {
|
||||
write!(
|
||||
out,
|
||||
"{}",
|
||||
CstNodeRange {
|
||||
opts,
|
||||
has_field_name: cursor.field_name().is_some(),
|
||||
is_named,
|
||||
is_multiline: true,
|
||||
total_width,
|
||||
range: node_range,
|
||||
}
|
||||
)?;
|
||||
}
|
||||
for _ in 0..=indent_level {
|
||||
write!(out, " ")?;
|
||||
}
|
||||
} else {
|
||||
write!(out, " ")?;
|
||||
}
|
||||
let formatted_line = render_line_feed(line, opts);
|
||||
write!(
|
||||
out,
|
||||
"{}{}{}",
|
||||
paint(quote_color, quote),
|
||||
paint(color, CstLineFeed { source: line, opts }),
|
||||
paint(quote_color, quote),
|
||||
"{}{}{}{}{}{}",
|
||||
if multiline { "\n" } else { " " },
|
||||
if multiline && !opts.no_ranges {
|
||||
render_node_range(opts, cursor, is_named, true, total_width, node_range)
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
if multiline {
|
||||
" ".repeat(indent_level + 1)
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
paint(quote_color, &String::from(quote)),
|
||||
paint(color, &render_node_text(&formatted_line)),
|
||||
paint(quote_color, &String::from(quote)),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
|
@ -918,93 +910,64 @@ fn write_node_text(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
struct CstLineFeed<'src, 'opt> {
|
||||
source: &'src str,
|
||||
opts: &'src ParseFileOptions<'opt>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CstLineFeed<'_, '_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
#[cfg(windows)]
|
||||
let lf = "\r\n";
|
||||
#[cfg(not(windows))]
|
||||
let lf = "\n";
|
||||
let painted = paint(self.opts.parse_theme.line_feed, CstNodeText(lf));
|
||||
let mut parts = self.source.split(lf);
|
||||
if let Some(first) = parts.next() {
|
||||
write!(f, "{}", CstNodeText(first))?;
|
||||
}
|
||||
for part in parts {
|
||||
write!(f, "{painted}{}", CstNodeText(part))?;
|
||||
}
|
||||
Ok(())
|
||||
fn render_line_feed(source: &str, opts: &ParseFileOptions) -> String {
|
||||
if cfg!(windows) {
|
||||
source.replace("\r\n", &paint(opts.parse_theme.line_feed, "\r\n"))
|
||||
} else {
|
||||
source.replace('\n', &paint(opts.parse_theme.line_feed, "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
struct CstNodeRange<'src, 'opt> {
|
||||
opts: &'src ParseFileOptions<'opt>,
|
||||
has_field_name: bool,
|
||||
fn render_node_range(
|
||||
opts: &ParseFileOptions,
|
||||
cursor: &TreeCursor,
|
||||
is_named: bool,
|
||||
is_multiline: bool,
|
||||
total_width: usize,
|
||||
range: Range,
|
||||
}
|
||||
) -> String {
|
||||
let has_field_name = cursor.field_name().is_some();
|
||||
let start = range.start_point;
|
||||
let end = range.end_point;
|
||||
let range_color = if is_named && !is_multiline && !has_field_name {
|
||||
opts.parse_theme.row_color_named
|
||||
} else {
|
||||
opts.parse_theme.row_color
|
||||
};
|
||||
|
||||
impl std::fmt::Display for CstNodeRange<'_, '_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let start = self.range.start_point;
|
||||
let end = self.range.end_point;
|
||||
let range_color = if self.is_named && !self.is_multiline && !self.has_field_name {
|
||||
self.opts.parse_theme.row_color_named
|
||||
} else {
|
||||
self.opts.parse_theme.row_color
|
||||
};
|
||||
let remaining_width = |row: usize, col: usize| {
|
||||
(self
|
||||
.total_width
|
||||
.saturating_sub(row.checked_ilog10().unwrap_or(0) as usize)
|
||||
.saturating_sub(col.checked_ilog10().unwrap_or(0) as usize))
|
||||
.max(1)
|
||||
};
|
||||
let remaining_width_start = remaining_width(start.row, start.column);
|
||||
let remaining_width_end = remaining_width(end.row, end.column);
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
paint(
|
||||
range_color,
|
||||
format_args!(
|
||||
"{}:{}{:remaining_width_start$}- {}:{}{:remaining_width_end$}",
|
||||
start.row, start.column, ' ', end.row, end.column, ' ',
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
let remaining_width = |row: usize, col: usize| {
|
||||
(total_width
|
||||
.saturating_sub(row.checked_ilog10().unwrap_or(0) as usize)
|
||||
.saturating_sub(col.checked_ilog10().unwrap_or(0) as usize))
|
||||
.max(1)
|
||||
};
|
||||
let remaining_width_start = remaining_width(start.row, start.column);
|
||||
let remaining_width_end = remaining_width(end.row, end.column);
|
||||
paint(
|
||||
range_color,
|
||||
&format!(
|
||||
"{}:{}{:remaining_width_start$}- {}:{}{:remaining_width_end$}",
|
||||
start.row, start.column, ' ', end.row, end.column, ' ',
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn cst_render_node(
|
||||
opts: &ParseFileOptions,
|
||||
cursor: &TreeCursor,
|
||||
cursor: &mut TreeCursor,
|
||||
source_code: &[u8],
|
||||
out: &mut impl Write,
|
||||
total_width: usize,
|
||||
indent_level: usize,
|
||||
in_error: bool,
|
||||
) -> io::Result<()> {
|
||||
) -> Result<()> {
|
||||
let node = cursor.node();
|
||||
let is_named = node.is_named();
|
||||
if !opts.no_ranges {
|
||||
write!(
|
||||
out,
|
||||
"{}",
|
||||
CstNodeRange {
|
||||
opts,
|
||||
has_field_name: cursor.field_name().is_some(),
|
||||
is_named,
|
||||
is_multiline: false,
|
||||
total_width,
|
||||
range: node.range(),
|
||||
}
|
||||
render_node_range(opts, cursor, is_named, false, total_width, node.range())
|
||||
)?;
|
||||
}
|
||||
write!(
|
||||
|
|
@ -1022,7 +985,7 @@ fn cst_render_node(
|
|||
write!(
|
||||
out,
|
||||
"{}",
|
||||
paint(opts.parse_theme.field, format_args!("{field_name}: "))
|
||||
paint(opts.parse_theme.field, &format!("{field_name}: "))
|
||||
)?;
|
||||
}
|
||||
|
||||
|
|
@ -1093,13 +1056,10 @@ pub fn perform_edit(tree: &mut Tree, input: &mut Vec<u8>, edit: &Edit) -> Result
|
|||
|
||||
fn parse_edit_flag(source_code: &[u8], flag: &str) -> Result<Edit> {
|
||||
let error = || {
|
||||
anyhow!(
|
||||
concat!(
|
||||
"Invalid edit string '{}'. ",
|
||||
"Edit strings must match the pattern '<START_BYTE_OR_POSITION> <REMOVED_LENGTH> <NEW_TEXT>'"
|
||||
),
|
||||
flag
|
||||
)
|
||||
anyhow!(concat!(
|
||||
"Invalid edit string '{}'. ",
|
||||
"Edit strings must match the pattern '<START_BYTE_OR_POSITION> <REMOVED_LENGTH> <NEW_TEXT>'"
|
||||
), flag)
|
||||
};
|
||||
|
||||
// Three whitespace-separated parts:
|
||||
|
|
@ -1137,25 +1097,30 @@ fn parse_edit_flag(source_code: &[u8], flag: &str) -> Result<Edit> {
|
|||
|
||||
pub fn offset_for_position(input: &[u8], position: Point) -> Result<usize> {
|
||||
let mut row = 0;
|
||||
let mut line_start = 0;
|
||||
for line_end in memchr::memchr_iter(b'\n', input) {
|
||||
if row == position.row {
|
||||
if position.column > line_end - line_start {
|
||||
return Err(anyhow!("Failed to address a column: {}", position.column));
|
||||
let mut offset = 0;
|
||||
let mut iter = memchr::memchr_iter(b'\n', input);
|
||||
loop {
|
||||
if let Some(pos) = iter.next() {
|
||||
if row < position.row {
|
||||
row += 1;
|
||||
offset = pos;
|
||||
continue;
|
||||
}
|
||||
return Ok(line_start + position.column);
|
||||
}
|
||||
row += 1;
|
||||
line_start = line_end + 1;
|
||||
offset += 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if row != position.row {
|
||||
if position.row - row > 0 {
|
||||
return Err(anyhow!("Failed to address a row: {}", position.row));
|
||||
}
|
||||
if position.column > input.len() - line_start {
|
||||
if let Some(pos) = iter.next() {
|
||||
if (pos - offset < position.column) || (input[offset] == b'\n' && position.column > 0) {
|
||||
return Err(anyhow!("Failed to address a column: {}", position.column));
|
||||
}
|
||||
} else if input.len() - offset < position.column {
|
||||
return Err(anyhow!("Failed to address a column over the end"));
|
||||
}
|
||||
Ok(line_start + position.column)
|
||||
Ok(offset + position.column)
|
||||
}
|
||||
|
||||
pub fn position_for_offset(input: &[u8], offset: usize) -> Result<Point> {
|
||||
|
|
@ -1175,45 +1140,3 @@ pub fn position_for_offset(input: &[u8], offset: usize) -> Result<Point> {
|
|||
};
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{offset_for_position, parse_edit_flag};
|
||||
use tree_sitter::Point;
|
||||
|
||||
#[test]
|
||||
fn offset_for_position_uses_zero_based_line_and_column_coordinates() {
|
||||
let input = b"abc\n";
|
||||
assert_eq!(
|
||||
offset_for_position(input, Point { row: 0, column: 0 }).unwrap(),
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
offset_for_position(input, Point { row: 0, column: 1 }).unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
offset_for_position(input, Point { row: 0, column: 3 }).unwrap(),
|
||||
3
|
||||
);
|
||||
assert_eq!(
|
||||
offset_for_position(input, Point { row: 1, column: 0 }).unwrap(),
|
||||
4
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offset_for_position_rejects_out_of_bounds_coordinates() {
|
||||
let input = b"abc\ndef";
|
||||
assert!(offset_for_position(input, Point { row: 0, column: 4 }).is_err());
|
||||
assert!(offset_for_position(input, Point { row: 2, column: 0 }).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_edit_flag_resolves_first_line_positions() {
|
||||
let edit = parse_edit_flag(b"abc\n", "0,0 0 X").unwrap();
|
||||
assert_eq!(edit.position, 0);
|
||||
assert_eq!(edit.deleted_length, 0);
|
||||
assert_eq!(edit.inserted_text, b"X");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@
|
|||
--light-scrollbar-track: #f1f1f1;
|
||||
--light-scrollbar-thumb: #c1c1c1;
|
||||
--light-scrollbar-thumb-hover: #a8a8a8;
|
||||
--light-tree-row-bg: #e3f2fd;
|
||||
|
||||
|
||||
--dark-bg: #1d1f21;
|
||||
--dark-border: #2d2d2d;
|
||||
--dark-text: #c5c8c6;
|
||||
|
|
@ -29,7 +28,6 @@
|
|||
--dark-scrollbar-track: #25282c;
|
||||
--dark-scrollbar-thumb: #4a4d51;
|
||||
--dark-scrollbar-thumb-hover: #5a5d61;
|
||||
--dark-tree-row-bg: #373737;
|
||||
|
||||
--primary-color: #0550ae;
|
||||
--primary-color-alpha: rgba(5, 80, 174, 0.1);
|
||||
|
|
@ -44,7 +42,6 @@
|
|||
--text-color: var(--dark-text);
|
||||
--panel-bg: var(--dark-panel-bg);
|
||||
--code-bg: var(--dark-code-bg);
|
||||
--tree-row-bg: var(--dark-tree-row-bg);
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
|
|
@ -53,7 +50,6 @@
|
|||
--text-color: var(--light-text);
|
||||
--panel-bg: white;
|
||||
--code-bg: white;
|
||||
--tree-row-bg: var(--light-tree-row-bg);
|
||||
}
|
||||
|
||||
/* Base Styles */
|
||||
|
|
@ -279,7 +275,7 @@
|
|||
}
|
||||
|
||||
#output-container a.highlighted {
|
||||
background-color: #cae2ff;
|
||||
background-color: #d9d9d9;
|
||||
color: red;
|
||||
border-radius: 3px;
|
||||
text-decoration: underline;
|
||||
|
|
@ -350,7 +346,7 @@
|
|||
}
|
||||
|
||||
& #output-container a.highlighted {
|
||||
background-color: #656669;
|
||||
background-color: #373b41;
|
||||
color: red;
|
||||
}
|
||||
|
||||
|
|
@ -377,9 +373,6 @@
|
|||
color: var(--dark-text);
|
||||
}
|
||||
}
|
||||
.tree-row:has(.highlighted) {
|
||||
background-color: var(--tree-row-bg);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
|
@ -470,7 +463,7 @@
|
|||
<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>LANGUAGE_BASE_URL = "";</script>
|
||||
<script type="module" src="playground.js"></script>
|
||||
<script type="module">
|
||||
import * as TreeSitter from './web-tree-sitter.js';
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ use std::{
|
|||
env, fs,
|
||||
net::TcpListener,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr as _,
|
||||
str::{self, FromStr as _},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use log::{error, info};
|
||||
use tiny_http::{Header, Response, Server};
|
||||
|
||||
|
|
|
|||
|
|
@ -75,18 +75,18 @@ pub fn query_file_at_path(
|
|||
if opts.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 = mat.captures[*capture_index];
|
||||
let capture_name = &query.capture_names()[capture.index as usize];
|
||||
if !opts.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("")
|
||||
)?;
|
||||
&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("")
|
||||
)?;
|
||||
}
|
||||
if should_test {
|
||||
results.push(query_testing::CaptureInfo {
|
||||
|
|
@ -102,18 +102,18 @@ pub fn query_file_at_path(
|
|||
if !opts.quiet && !should_test {
|
||||
writeln!(&mut stdout, " pattern: {}", m.pattern_index)?;
|
||||
}
|
||||
for capture in m.captures() {
|
||||
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 !opts.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("")
|
||||
)?;
|
||||
&mut stdout,
|
||||
" capture: {} - {capture_name}, start: {start}, end: {end}, text: `{}`",
|
||||
capture.index,
|
||||
capture.node.utf8_text(&source_code).unwrap_or("")
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
&mut stdout,
|
||||
|
|
@ -142,9 +142,7 @@ pub fn query_file_at_path(
|
|||
};
|
||||
// Invariant: `test_summary` will always be `Some` when `should_test` is true
|
||||
let test_summary = test_summary.unwrap();
|
||||
let assertions =
|
||||
query_testing::parse_position_comments(&mut parser, language, source_code.as_slice())?;
|
||||
match query_testing::assert_expected_captures(&results, &assertions) {
|
||||
match query_testing::assert_expected_captures(&results, path, &mut parser, language) {
|
||||
Ok(assertion_count) => {
|
||||
test_summary.query_results.add_case(TestResult {
|
||||
name: path_name.to_string(),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::LazyLock;
|
||||
use std::{fs, path::Path, sync::LazyLock};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use anyhow::{anyhow, Result};
|
||||
use bstr::{BStr, ByteSlice};
|
||||
use regex::Regex;
|
||||
use tree_sitter::{Language, Parser, Point};
|
||||
|
|
@ -106,66 +106,66 @@ pub fn parse_position_comments(
|
|||
let node = cursor.node();
|
||||
|
||||
// Find every comment node.
|
||||
if node.kind().to_lowercase().contains("comment")
|
||||
&& let Ok(text) = node.utf8_text(source)
|
||||
{
|
||||
let mut position = node.start_position();
|
||||
if position.row > 0 {
|
||||
// Find the arrow character ("^" or "<-") in the comment. A left arrow
|
||||
// refers to the column where the comment node starts. An up arrow refers
|
||||
// to its own column.
|
||||
let mut has_left_caret = false;
|
||||
let mut has_arrow = false;
|
||||
let mut negative = false;
|
||||
let mut arrow_end = 0;
|
||||
let mut arrow_count = 1;
|
||||
for (i, c) in text.char_indices() {
|
||||
arrow_end = i + 1;
|
||||
if c == '-' && has_left_caret {
|
||||
has_arrow = true;
|
||||
break;
|
||||
if node.kind().to_lowercase().contains("comment") {
|
||||
if let Ok(text) = node.utf8_text(source) {
|
||||
let mut position = node.start_position();
|
||||
if position.row > 0 {
|
||||
// Find the arrow character ("^" or "<-") in the comment. A left arrow
|
||||
// refers to the column where the comment node starts. An up arrow refers
|
||||
// to its own column.
|
||||
let mut has_left_caret = false;
|
||||
let mut has_arrow = false;
|
||||
let mut negative = false;
|
||||
let mut arrow_end = 0;
|
||||
let mut arrow_count = 1;
|
||||
for (i, c) in text.char_indices() {
|
||||
arrow_end = i + 1;
|
||||
if c == '-' && has_left_caret {
|
||||
has_arrow = true;
|
||||
break;
|
||||
}
|
||||
if c == '^' {
|
||||
has_arrow = true;
|
||||
position.column += i;
|
||||
// Continue counting remaining arrows and update their end column
|
||||
for (_, c) in text[arrow_end..].char_indices() {
|
||||
if c != '^' {
|
||||
arrow_end += arrow_count - 1;
|
||||
break;
|
||||
}
|
||||
arrow_count += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
has_left_caret = c == '<';
|
||||
}
|
||||
if c == '^' {
|
||||
has_arrow = true;
|
||||
position.column += i;
|
||||
// Continue counting remaining arrows and update their end column
|
||||
for (_, c) in text[arrow_end..].char_indices() {
|
||||
if c != '^' {
|
||||
arrow_end += arrow_count - 1;
|
||||
|
||||
// find any ! after arrows but before capture name
|
||||
if has_arrow {
|
||||
for (i, c) in text[arrow_end..].char_indices() {
|
||||
if c == '!' {
|
||||
negative = true;
|
||||
arrow_end += i + 1;
|
||||
break;
|
||||
} else if !c.is_whitespace() {
|
||||
break;
|
||||
}
|
||||
arrow_count += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
has_left_caret = c == '<';
|
||||
}
|
||||
|
||||
// find any ! after arrows but before capture name
|
||||
if has_arrow {
|
||||
for (i, c) in text[arrow_end..].char_indices() {
|
||||
if c == '!' {
|
||||
negative = true;
|
||||
arrow_end += i + 1;
|
||||
break;
|
||||
} else if !c.is_whitespace() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the comment node contains an arrow and a highlight name, record the
|
||||
// highlight name and the position.
|
||||
if let (true, Some(mat)) =
|
||||
(has_arrow, CAPTURE_NAME_REGEX.find(&text[arrow_end..]))
|
||||
{
|
||||
assertion_ranges.push((node.start_position(), node.end_position()));
|
||||
result.push(Assertion {
|
||||
position: to_utf8_point(position, source),
|
||||
length: arrow_count,
|
||||
negative,
|
||||
expected_capture_name: mat.as_str().to_string(),
|
||||
});
|
||||
// If the comment node contains an arrow and a highlight name, record the
|
||||
// highlight name and the position.
|
||||
if let (true, Some(mat)) =
|
||||
(has_arrow, CAPTURE_NAME_REGEX.find(&text[arrow_end..]))
|
||||
{
|
||||
assertion_ranges.push((node.start_position(), node.end_position()));
|
||||
result.push(Assertion {
|
||||
position: to_utf8_point(position, source),
|
||||
length: arrow_count,
|
||||
negative,
|
||||
expected_capture_name: mat.as_str().to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -219,14 +219,19 @@ pub fn parse_position_comments(
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn assert_expected_captures(infos: &[CaptureInfo], assertions: &[Assertion]) -> Result<usize> {
|
||||
for assertion in assertions {
|
||||
pub fn assert_expected_captures(
|
||||
infos: &[CaptureInfo],
|
||||
path: &Path,
|
||||
parser: &mut Parser,
|
||||
language: &Language,
|
||||
) -> Result<usize> {
|
||||
let contents = fs::read_to_string(path)?;
|
||||
let pairs = parse_position_comments(parser, language, contents.as_bytes())?;
|
||||
for assertion in &pairs {
|
||||
if let Some(found) = &infos.iter().find(|p| {
|
||||
let assertion_end = Utf8Point::new(
|
||||
assertion.position.row,
|
||||
assertion.position.column + assertion.length - 1,
|
||||
);
|
||||
assertion.position >= p.start && assertion_end < p.end
|
||||
assertion.position >= p.start
|
||||
&& (assertion.position.row < p.end.row
|
||||
|| assertion.position.column + assertion.length - 1 < p.end.column)
|
||||
}) {
|
||||
if assertion.expected_capture_name != found.name && found.name != "name" {
|
||||
return Err(anyhow!(
|
||||
|
|
@ -245,24 +250,5 @@ pub fn assert_expected_captures(infos: &[CaptureInfo], assertions: &[Assertion])
|
|||
));
|
||||
}
|
||||
}
|
||||
Ok(assertions.len())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Assertion, CaptureInfo, Utf8Point, assert_expected_captures};
|
||||
|
||||
#[test]
|
||||
fn test_assertion_after_multiline_capture_does_not_match() {
|
||||
let captures = [CaptureInfo {
|
||||
name: "foo".to_string(),
|
||||
start: Utf8Point::new(0, 0),
|
||||
end: Utf8Point::new(1, 1),
|
||||
}];
|
||||
let assertions = [Assertion::new(2, 0, 1, false, "foo".to_string())];
|
||||
|
||||
let result = assert_expected_captures(&captures, &assertions);
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
Ok(pairs.len())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ use std::{
|
|||
fs,
|
||||
io::{self, Write},
|
||||
path::Path,
|
||||
sync::{Arc, atomic::AtomicUsize},
|
||||
str,
|
||||
sync::{atomic::AtomicUsize, Arc},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
"""PARSER_DESCRIPTION"""
|
||||
|
||||
from importlib.resources import files as _files
|
||||
|
||||
from ._binding import language
|
||||
|
||||
|
||||
def _get_query(name, file):
|
||||
files = globals().get("_files")
|
||||
if files is None:
|
||||
from importlib.resources import files
|
||||
globals()["_files"] = files
|
||||
try:
|
||||
query = files(f"{__package__}") / file
|
||||
query = _files(f"{__package__}") / file
|
||||
globals()[name] = query.read_text()
|
||||
except FileNotFoundError:
|
||||
globals()[name] = None
|
||||
|
|
|
|||
|
|
@ -1,18 +1,25 @@
|
|||
[package]
|
||||
authors = [ "PARSER_AUTHOR_NAME PARSER_AUTHOR_EMAIL" ]
|
||||
autoexamples = false
|
||||
categories = [ "parser-implementations", "parsing", "text-editors" ]
|
||||
description = "PARSER_DESCRIPTION"
|
||||
edition = "2024"
|
||||
keywords = [ "incremental", "parsing", "tree-sitter", "PARSER_NAME" ]
|
||||
license = "PARSER_LICENSE"
|
||||
name = "tree-sitter-PARSER_NAME"
|
||||
readme = "README.md"
|
||||
repository = "PARSER_URL"
|
||||
description = "PARSER_DESCRIPTION"
|
||||
version = "PARSER_VERSION"
|
||||
authors = ["PARSER_AUTHOR_NAME PARSER_AUTHOR_EMAIL"]
|
||||
license = "PARSER_LICENSE"
|
||||
readme = "README.md"
|
||||
keywords = ["incremental", "parsing", "tree-sitter", "PARSER_NAME"]
|
||||
categories = ["parser-implementations", "parsing", "text-editors"]
|
||||
repository = "PARSER_URL"
|
||||
edition = "2021"
|
||||
autoexamples = false
|
||||
|
||||
build = "bindings/rust/build.rs"
|
||||
include = [ "bindings/rust/*", "grammar.js", "queries/*", "src/*", "tree-sitter.json", "/LICENSE" ]
|
||||
include = [
|
||||
"bindings/rust/*",
|
||||
"grammar.js",
|
||||
"queries/*",
|
||||
"src/*",
|
||||
"tree-sitter.json",
|
||||
"/LICENSE",
|
||||
]
|
||||
|
||||
[lib]
|
||||
path = "bindings/rust/lib.rs"
|
||||
|
|
|
|||
|
|
@ -11,8 +11,18 @@ fn main() {
|
|||
let Ok(wasm_headers) = std::env::var("DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS") else {
|
||||
panic!("Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS must be set by the language crate");
|
||||
};
|
||||
let Ok(wasm_src) =
|
||||
std::env::var("DEP_TREE_SITTER_LANGUAGE_WASM_SRC").map(std::path::PathBuf::from)
|
||||
else {
|
||||
panic!("Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_SRC must be set by the language crate");
|
||||
};
|
||||
|
||||
c_config.include(&wasm_headers);
|
||||
c_config.files([
|
||||
wasm_src.join("stdio.c"),
|
||||
wasm_src.join("stdlib.c"),
|
||||
wasm_src.join("string.c"),
|
||||
]);
|
||||
}
|
||||
|
||||
let parser_path = src_dir.join("parser.c");
|
||||
|
|
|
|||
|
|
@ -4,51 +4,46 @@ pub fn build(b: *std.Build) !void {
|
|||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
var threaded: std.Io.Threaded = .init(b.allocator, .{});
|
||||
defer threaded.deinit();
|
||||
const io = threaded.io();
|
||||
|
||||
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 library_name = "tree-sitter-PARSER_NAME";
|
||||
|
||||
var grammar = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true,
|
||||
.pic = if (shared) true else null,
|
||||
});
|
||||
const lib: *std.Build.Step.Compile = b.addLibrary(.{
|
||||
.name = library_name,
|
||||
.linkage = if (shared) .dynamic else .static,
|
||||
.root_module = grammar,
|
||||
.root_module = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true,
|
||||
.pic = if (shared) true else null,
|
||||
}),
|
||||
});
|
||||
|
||||
grammar.addCSourceFile(.{
|
||||
lib.addCSourceFile(.{
|
||||
.file = b.path("src/parser.c"),
|
||||
.flags = &.{"-std=c11"},
|
||||
});
|
||||
if (fileExists(b, io, "src/scanner.c")) {
|
||||
grammar.addCSourceFile(.{
|
||||
if (fileExists(b, "src/scanner.c")) {
|
||||
lib.addCSourceFile(.{
|
||||
.file = b.path("src/scanner.c"),
|
||||
.flags = &.{"-std=c11"},
|
||||
});
|
||||
}
|
||||
|
||||
if (reuse_alloc) {
|
||||
grammar.addCMacro("TREE_SITTER_REUSE_ALLOCATOR", "");
|
||||
lib.root_module.addCMacro("TREE_SITTER_REUSE_ALLOCATOR", "");
|
||||
}
|
||||
if (optimize == .Debug) {
|
||||
grammar.addCMacro("TREE_SITTER_DEBUG", "");
|
||||
lib.root_module.addCMacro("TREE_SITTER_DEBUG", "");
|
||||
}
|
||||
|
||||
grammar.addIncludePath(b.path("src"));
|
||||
lib.addIncludePath(b.path("src"));
|
||||
|
||||
b.installArtifact(lib);
|
||||
b.installFile("src/node-types.json", "node-types.json");
|
||||
|
||||
if (fileExists(b, io, "queries")) {
|
||||
if (fileExists(b, "queries")) {
|
||||
b.installDirectory(.{
|
||||
.source_dir = b.path("queries"),
|
||||
.install_dir = .prefix,
|
||||
|
|
@ -74,10 +69,16 @@ pub fn build(b: *std.Build) !void {
|
|||
tests.root_module.addImport(library_name, module);
|
||||
|
||||
// HACK: fetch tree-sitter dependency only when testing this module
|
||||
if (b.option(bool, "test", "Fetch test dependencies") orelse false) {
|
||||
const ts_dep = b.lazyDependency("tree_sitter", .{});
|
||||
if (ts_dep) |dep|
|
||||
tests.root_module.addImport("tree-sitter", dep.module("tree_sitter"));
|
||||
if (b.pkg_hash.len == 0) {
|
||||
var args = try std.process.argsWithAllocator(b.allocator);
|
||||
defer args.deinit();
|
||||
while (args.next()) |a| {
|
||||
if (std.mem.eql(u8, a, "test")) {
|
||||
const ts_dep = b.lazyDependency("tree_sitter", .{}) orelse continue;
|
||||
tests.root_module.addImport("tree-sitter", ts_dep.module("tree-sitter"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const run_tests = b.addRunArtifact(tests);
|
||||
|
|
@ -85,8 +86,8 @@ pub fn build(b: *std.Build) !void {
|
|||
test_step.dependOn(&run_tests.step);
|
||||
}
|
||||
|
||||
inline fn fileExists(b: *std.Build, io: std.Io, filename: []const u8) bool {
|
||||
inline fn fileExists(b: *std.Build, filename: []const u8) bool {
|
||||
const dir = b.build_root.handle;
|
||||
dir.access(io, filename, .{}) catch return false;
|
||||
dir.access(filename, .{}) catch return false;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
.{
|
||||
.name = .tree_sitter_PARSER_NAME,
|
||||
.fingerprint = PARSER_FINGERPRINT,
|
||||
.minimum_zig_version = "0.16.0",
|
||||
.version = "PARSER_VERSION",
|
||||
.dependencies = .{
|
||||
.tree_sitter = .{
|
||||
.url = "git+https://github.com/tree-sitter/zig-tree-sitter#0cf58172e61f6fdd16f681cde42b4acb531a23db",
|
||||
.hash = "tree_sitter-0.26.0-8heIf3CaAQDeVTQc0DMSBhbQAEx5aF-dTen4_LPxMgrv",
|
||||
.url = "git+https://github.com/tree-sitter/zig-tree-sitter#b4b72c903e69998fc88e27e154a5e3cc9166551b",
|
||||
.hash = "tree_sitter-0.25.0-8heIf51vAQConvVIgvm-9mVIbqh7yabZYqPXfOpS3YoG",
|
||||
.lazy = true,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ endif()
|
|||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI" REQUIRED)
|
||||
find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI")
|
||||
|
||||
add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/node-types.json"
|
||||
|
|
|
|||
2
crates/cli/src/templates/index.d.ts
vendored
2
crates/cli/src/templates/index.d.ts
vendored
|
|
@ -21,7 +21,7 @@ type NodeInfo =
|
|||
/**
|
||||
* The tree-sitter language object for this grammar.
|
||||
*
|
||||
* @see {@linkcode https://tree-sitter.github.io/node-tree-sitter/interfaces/Language.html Parser.Language}
|
||||
* @see {@linkcode https://tree-sitter.github.io/node-tree-sitter/interfaces/Parser.Language.html Parser.Language}
|
||||
*
|
||||
* @example
|
||||
* import Parser from "tree-sitter";
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
use tree_sitter_language::LanguageFn;
|
||||
|
||||
unsafe extern "C" {
|
||||
extern "C" {
|
||||
fn tree_sitter_PARSER_NAME() -> *const ();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
// swift-tools-version:5.6
|
||||
// swift-tools-version:5.3
|
||||
|
||||
import Foundation
|
||||
import PackageDescription
|
||||
|
||||
let dir = Context.packageDirectory
|
||||
var sources = ["src/parser.c"]
|
||||
if FileManager.default.fileExists(atPath: "\(dir)/src/scanner.c") {
|
||||
if FileManager.default.fileExists(atPath: "src/scanner.c") {
|
||||
sources.append("src/scanner.c")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=62.4.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
requires = [ "setuptools>=62.4.0", "wheel" ]
|
||||
|
||||
[project]
|
||||
authors = [ { email = "PARSER_AUTHOR_EMAIL", name = "PARSER_AUTHOR_NAME" } ]
|
||||
name = "tree-sitter-PARSER_NAME"
|
||||
description = "PARSER_DESCRIPTION"
|
||||
version = "PARSER_VERSION"
|
||||
keywords = ["incremental", "parsing", "tree-sitter", "PARSER_NAME"]
|
||||
classifiers = [
|
||||
"Intended Audience :: Developers",
|
||||
"Topic :: Software Development :: Compilers",
|
||||
"Topic :: Text Processing :: Linguistic",
|
||||
"Typing :: Typed",
|
||||
]
|
||||
description = "PARSER_DESCRIPTION"
|
||||
keywords = [ "incremental", "parsing", "tree-sitter", "PARSER_NAME" ]
|
||||
license.text = "PARSER_LICENSE"
|
||||
name = "tree-sitter-PARSER_NAME"
|
||||
readme = "README.md"
|
||||
authors = [{ name = "PARSER_AUTHOR_NAME", email = "PARSER_AUTHOR_EMAIL" }]
|
||||
requires-python = ">=3.10"
|
||||
version = "PARSER_VERSION"
|
||||
license.text = "PARSER_LICENSE"
|
||||
readme = "README.md"
|
||||
|
||||
[project.urls]
|
||||
Funding = "FUNDING_URL"
|
||||
Homepage = "PARSER_URL"
|
||||
Funding = "FUNDING_URL"
|
||||
|
||||
[project.optional-dependencies]
|
||||
core = [ "tree-sitter~=0.24" ]
|
||||
core = ["tree-sitter~=0.24"]
|
||||
|
||||
[tool.cibuildwheel]
|
||||
build = "cp310-*"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,12 +1,12 @@
|
|||
use std::{fs, path::Path};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use anyhow::{anyhow, Result};
|
||||
use tree_sitter::Point;
|
||||
use tree_sitter_highlight::{Highlight, HighlightConfiguration, HighlightEvent, Highlighter};
|
||||
use tree_sitter_loader::{Config, Loader};
|
||||
|
||||
use crate::{
|
||||
query_testing::{Assertion, Utf8Point, parse_position_comments, to_utf8_point},
|
||||
query_testing::{parse_position_comments, to_utf8_point, Assertion, Utf8Point},
|
||||
test::{TestInfo, TestOutcome, TestResult, TestSummary},
|
||||
util,
|
||||
};
|
||||
|
|
@ -120,7 +120,11 @@ pub fn test_highlights(
|
|||
}
|
||||
}
|
||||
|
||||
if failed { Err(anyhow!("")) } else { Ok(()) }
|
||||
if failed {
|
||||
Err(anyhow!(""))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iterate_assertions(
|
||||
|
|
@ -219,11 +223,9 @@ pub fn get_highlight_positions(
|
|||
let mut highlight_stack = Vec::new();
|
||||
let source = String::from_utf8_lossy(source);
|
||||
let mut char_indices = source.char_indices();
|
||||
for event in
|
||||
highlighter.highlight(highlight_config, source.as_bytes(), None, None, |string| {
|
||||
loader.highlight_config_for_injection_string(string)
|
||||
})?
|
||||
{
|
||||
for event in highlighter.highlight(highlight_config, source.as_bytes(), None, |string| {
|
||||
loader.highlight_config_for_injection_string(string)
|
||||
})? {
|
||||
match event? {
|
||||
HighlightEvent::HighlightStart(h) => highlight_stack.push(h),
|
||||
HighlightEvent::HighlightEnd => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use std::{fs, path::Path};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use anyhow::{anyhow, Result};
|
||||
use tree_sitter_loader::{Config, Loader};
|
||||
use tree_sitter_tags::{TagsConfiguration, TagsContext};
|
||||
|
||||
use crate::{
|
||||
query_testing::{Assertion, Utf8Point, parse_position_comments, to_utf8_point},
|
||||
query_testing::{parse_position_comments, to_utf8_point, Assertion, Utf8Point},
|
||||
test::{TestInfo, TestOutcome, TestResult, TestSummary},
|
||||
util,
|
||||
};
|
||||
|
|
@ -113,7 +113,11 @@ pub fn test_tags(
|
|||
}
|
||||
}
|
||||
|
||||
if failed { Err(anyhow!("")) } else { Ok(()) }
|
||||
if failed {
|
||||
Err(anyhow!(""))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_tag(
|
||||
|
|
|
|||
|
|
@ -17,12 +17,13 @@ mod tree_test;
|
|||
#[cfg(feature = "wasm")]
|
||||
mod wasm_language_test;
|
||||
|
||||
use tree_sitter_generate::{GenerateResult, OptLevel};
|
||||
use tree_sitter_generate::GenerateResult;
|
||||
|
||||
pub use crate::fuzz::{
|
||||
ITERATION_COUNT, allocations,
|
||||
allocations,
|
||||
edits::{get_random_edit, invert_edit},
|
||||
random::Rand,
|
||||
ITERATION_COUNT,
|
||||
};
|
||||
|
||||
pub use helpers::fixtures::get_language;
|
||||
|
|
@ -30,10 +31,5 @@ pub use helpers::fixtures::get_language;
|
|||
/// This is a simple wrapper around [`tree_sitter_generate::generate_parser_for_grammar`], because
|
||||
/// our tests do not need to pass in a version number, only the grammar JSON.
|
||||
fn generate_parser(grammar_json: &str) -> GenerateResult<(String, String)> {
|
||||
tree_sitter_generate::generate_parser_for_grammar(
|
||||
grammar_json,
|
||||
Some((0, 0, 0)),
|
||||
OptLevel::default(),
|
||||
&mut Vec::new(),
|
||||
)
|
||||
tree_sitter_generate::generate_parser_for_grammar(grammar_json, Some((0, 0, 0)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,25 +2,24 @@ use std::{collections::HashMap, env, fs};
|
|||
|
||||
use anyhow::Context;
|
||||
use tree_sitter::Parser;
|
||||
use tree_sitter_generate::OptLevel;
|
||||
use tree_sitter_proc_macro::test_with_seed;
|
||||
|
||||
use crate::{
|
||||
fuzz::{
|
||||
EDIT_COUNT, EXAMPLE_EXCLUDE, EXAMPLE_INCLUDE, ITERATION_COUNT, LANGUAGE_FILTER,
|
||||
LOG_GRAPH_ENABLED, START_SEED,
|
||||
corpus_test::{
|
||||
check_changed_ranges, check_consistent_sizes, get_parser, set_included_ranges,
|
||||
},
|
||||
edits::{get_random_edit, invert_edit},
|
||||
flatten_tests, new_seed,
|
||||
random::Rand,
|
||||
EDIT_COUNT, EXAMPLE_EXCLUDE, EXAMPLE_INCLUDE, ITERATION_COUNT, LANGUAGE_FILTER,
|
||||
LOG_GRAPH_ENABLED, START_SEED,
|
||||
},
|
||||
parse::perform_edit,
|
||||
test::{DiffKey, TestDiff, parse_tests, render_test_output},
|
||||
test::{parse_tests, strip_sexp_fields, DiffKey, TestDiff},
|
||||
tests::{
|
||||
allocations,
|
||||
helpers::fixtures::{SCRATCH_BASE_DIR, fixtures_dir, get_language, get_test_language},
|
||||
helpers::fixtures::{fixtures_dir, get_language, get_test_language, SCRATCH_BASE_DIR},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -121,10 +120,10 @@ pub fn test_language_corpus(
|
|||
skipped: Option<&[&str]>,
|
||||
language_dir: Option<&str>,
|
||||
) {
|
||||
if let Some(filter) = LANGUAGE_FILTER.as_ref()
|
||||
&& language_name != filter
|
||||
{
|
||||
return;
|
||||
if let Some(filter) = LANGUAGE_FILTER.as_ref() {
|
||||
if language_name != filter {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let language_dir = language_dir.unwrap_or_default();
|
||||
|
|
@ -186,17 +185,38 @@ pub fn test_language_corpus(
|
|||
println!();
|
||||
for (test_index, test) in tests.iter().enumerate() {
|
||||
let test_name = format!("{language_name} - {}", test.name);
|
||||
if let Some(skipped) = skipped.as_mut()
|
||||
&& let Some(counter) = skipped.get_mut(test_name.as_str())
|
||||
{
|
||||
println!(" {test_index}. {test_name} - SKIPPED");
|
||||
*counter += 1;
|
||||
continue;
|
||||
if let Some(skipped) = skipped.as_mut() {
|
||||
if let Some(counter) = skipped.get_mut(test_name.as_str()) {
|
||||
println!(" {test_index}. {test_name} - SKIPPED");
|
||||
*counter += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
println!(" {test_index}. {test_name}");
|
||||
|
||||
let passed = allocations::record(|| test.check_initial_parse(&language, &test_name, true));
|
||||
let passed = allocations::record(|| {
|
||||
let mut log_session = None;
|
||||
let mut parser = get_parser(&mut log_session, "log.html");
|
||||
parser.set_language(&language).unwrap();
|
||||
set_included_ranges(&mut parser, &test.input, test.template_delimiters);
|
||||
|
||||
let tree = parser.parse(&test.input, None).unwrap();
|
||||
let mut actual_output = tree.root_node().to_sexp();
|
||||
if !test.has_fields {
|
||||
actual_output = strip_sexp_fields(&actual_output);
|
||||
}
|
||||
|
||||
if actual_output != test.output {
|
||||
println!("Incorrect initial parse for {test_name}");
|
||||
DiffKey::print();
|
||||
println!("{}", TestDiff::new(&actual_output, &test.output));
|
||||
println!();
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
|
||||
if !passed {
|
||||
failure_count += 1;
|
||||
|
|
@ -254,9 +274,7 @@ pub fn test_language_corpus(
|
|||
// Check that the new tree is consistent.
|
||||
check_consistent_sizes(&tree2, &input);
|
||||
if let Err(message) = check_changed_ranges(&tree, &tree2, &input) {
|
||||
println!(
|
||||
"\nUnexpected scope change in seed {seed} with start seed {start_seed}\n{message}\n\n",
|
||||
);
|
||||
println!("\nUnexpected scope change in seed {seed} with start seed {start_seed}\n{message}\n\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -272,8 +290,10 @@ pub fn test_language_corpus(
|
|||
let tree3 = parser.parse(&input, Some(&tree2)).unwrap();
|
||||
|
||||
// Verify that the final tree matches the expectation from the corpus.
|
||||
let actual_output =
|
||||
render_test_output(&input, &tree3, test.cst, test.has_fields).unwrap();
|
||||
let mut actual_output = tree3.root_node().to_sexp();
|
||||
if !test.has_fields {
|
||||
actual_output = strip_sexp_fields(&actual_output);
|
||||
}
|
||||
|
||||
if actual_output != test.output {
|
||||
println!("Incorrect parse for {test_name} - seed {seed}");
|
||||
|
|
@ -286,9 +306,7 @@ pub fn test_language_corpus(
|
|||
// Check that the edited tree is consistent.
|
||||
check_consistent_sizes(&tree3, &input);
|
||||
if let Err(message) = check_changed_ranges(&tree2, &tree3, &input) {
|
||||
println!(
|
||||
"Unexpected scope change in seed {seed} with start seed {start_seed}\n{message}\n\n"
|
||||
);
|
||||
println!("Unexpected scope change in seed {seed} with start seed {start_seed}\n{message}\n\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -333,10 +351,10 @@ fn test_feature_corpus_files() {
|
|||
let language_name = entry.file_name();
|
||||
let language_name = language_name.to_str().unwrap();
|
||||
|
||||
if let Some(filter) = LANGUAGE_FILTER.as_ref()
|
||||
&& language_name != filter
|
||||
{
|
||||
continue;
|
||||
if let Some(filter) = LANGUAGE_FILTER.as_ref() {
|
||||
if language_name != filter {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let test_path = entry.path();
|
||||
|
|
@ -353,12 +371,8 @@ fn test_feature_corpus_files() {
|
|||
)
|
||||
})
|
||||
.unwrap();
|
||||
let generate_result = tree_sitter_generate::generate_parser_for_grammar(
|
||||
&grammar_json,
|
||||
Some((0, 0, 0)),
|
||||
OptLevel::default(),
|
||||
&mut Vec::new(),
|
||||
);
|
||||
let generate_result =
|
||||
tree_sitter_generate::generate_parser_for_grammar(&grammar_json, Some((0, 0, 0)));
|
||||
|
||||
if error_message_path.exists() {
|
||||
if EXAMPLE_INCLUDE.is_some() || EXAMPLE_EXCLUDE.is_some() {
|
||||
|
|
@ -402,8 +416,24 @@ fn test_feature_corpus_files() {
|
|||
for test in tests {
|
||||
eprintln!(" example: {:?}", test.name);
|
||||
|
||||
let passed =
|
||||
allocations::record(|| test.check_initial_parse(&language, &test.name, true));
|
||||
let passed = allocations::record(|| {
|
||||
let mut log_session = None;
|
||||
let mut parser = get_parser(&mut log_session, "log.html");
|
||||
parser.set_language(&language).unwrap();
|
||||
let tree = parser.parse(&test.input, None).unwrap();
|
||||
let mut actual_output = tree.root_node().to_sexp();
|
||||
if !test.has_fields {
|
||||
actual_output = strip_sexp_fields(&actual_output);
|
||||
}
|
||||
if actual_output == test.output {
|
||||
true
|
||||
} else {
|
||||
DiffKey::print();
|
||||
print!("{}", TestDiff::new(&actual_output, &test.output));
|
||||
println!();
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if !passed {
|
||||
failure_count += 1;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::ops::Range;
|
||||
use std::{ops::Range, str};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ReadRecorder<'a> {
|
||||
|
|
@ -20,7 +20,7 @@ impl<'a> ReadRecorder<'a> {
|
|||
if let Err(i) = self.indices_read.binary_search(&offset) {
|
||||
self.indices_read.insert(i, offset);
|
||||
}
|
||||
&self.content[offset..=offset]
|
||||
&self.content[offset..(offset + 1)]
|
||||
} else {
|
||||
&[]
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ impl<'a> ReadRecorder<'a> {
|
|||
let mut result = Vec::new();
|
||||
let mut last_range = Option::<Range<usize>>::None;
|
||||
for index in &self.indices_read {
|
||||
if let Some(range) = &mut last_range {
|
||||
if let Some(ref mut range) = &mut last_range {
|
||||
if range.end == *index {
|
||||
range.end += 1;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use std::{
|
||||
collections::HashSet,
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
sync::{LazyLock, Mutex},
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use tree_sitter::Language;
|
||||
use tree_sitter_generate::{ALLOC_HEADER, ARRAY_HEADER, load_grammar_file};
|
||||
use tree_sitter_generate::{load_grammar_file, ALLOC_HEADER, ARRAY_HEADER};
|
||||
use tree_sitter_highlight::HighlightConfiguration;
|
||||
use tree_sitter_loader::{CompileConfig, Loader};
|
||||
use tree_sitter_tags::TagsConfiguration;
|
||||
|
|
@ -24,10 +23,6 @@ static TEST_LOADER: LazyLock<Loader> = LazyLock::new(|| {
|
|||
loader
|
||||
});
|
||||
|
||||
// Prevents parallel tests from racing on the same per-grammar
|
||||
// `src_dir/tree_sitter/` and observing a half-rewritten header.
|
||||
static WRITTEN_HEADER_DIRS: LazyLock<Mutex<HashSet<PathBuf>>> = LazyLock::new(Default::default);
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
pub static ENGINE: LazyLock<tree_sitter::wasmtime::Engine> = LazyLock::new(Default::default);
|
||||
|
||||
|
|
@ -139,22 +134,17 @@ fn get_test_language_internal(
|
|||
};
|
||||
|
||||
let header_path = src_dir.join("tree_sitter");
|
||||
if WRITTEN_HEADER_DIRS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(header_path.clone())
|
||||
{
|
||||
fs::create_dir_all(&header_path).unwrap();
|
||||
for (file, content) in [
|
||||
("alloc.h", ALLOC_HEADER),
|
||||
("array.h", ARRAY_HEADER),
|
||||
("parser.h", tree_sitter::PARSER_HEADER),
|
||||
] {
|
||||
let path = header_path.join(file);
|
||||
fs::write(&path, content)
|
||||
.with_context(|| format!("Failed to write {}", path.display()))
|
||||
.unwrap();
|
||||
}
|
||||
fs::create_dir_all(&header_path).unwrap();
|
||||
|
||||
for (file, content) in [
|
||||
("alloc.h", ALLOC_HEADER),
|
||||
("array.h", ARRAY_HEADER),
|
||||
("parser.h", tree_sitter::PARSER_HEADER),
|
||||
] {
|
||||
let file = header_path.join(file);
|
||||
fs::write(&file, content)
|
||||
.with_context(|| format!("Failed to write {:?}", file.file_name().unwrap()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let paths_to_check = if let Some(scanner_path) = &scanner_path {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
use std::{cmp::Ordering, fmt::Write, ops::Range};
|
||||
|
||||
use rand::{Rng, RngExt};
|
||||
use rand::prelude::Rng;
|
||||
use streaming_iterator::{IntoStreamingIterator, StreamingIterator};
|
||||
use tree_sitter::{
|
||||
Language, Node, Parser, Point, Query, QueryCapture, QueryCursor, QueryMatch, Tree, TreeCursor,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pattern<'a> {
|
||||
kind: Option<&'a str>,
|
||||
pub struct Pattern {
|
||||
kind: Option<&'static str>,
|
||||
named: bool,
|
||||
field: Option<&'a str>,
|
||||
field: Option<&'static str>,
|
||||
capture: Option<String>,
|
||||
children: Vec<Self>,
|
||||
}
|
||||
|
|
@ -25,17 +25,17 @@ const CAPTURE_NAMES: &[&str] = &[
|
|||
"one", "two", "three", "four", "five", "six", "seven", "eight",
|
||||
];
|
||||
|
||||
impl<'a> Pattern<'a> {
|
||||
pub fn random_pattern_in_tree(tree: &'a Tree, rng: &mut impl Rng) -> (Self, Range<Point>) {
|
||||
impl Pattern {
|
||||
pub fn random_pattern_in_tree(tree: &Tree, rng: &mut impl Rng) -> (Self, Range<Point>) {
|
||||
let mut cursor = tree.walk();
|
||||
|
||||
// Descend to the node at a random byte offset and depth.
|
||||
let mut max_depth = 0;
|
||||
let byte_offset = rng.random_range(0..cursor.node().end_byte());
|
||||
let byte_offset = rng.gen_range(0..cursor.node().end_byte());
|
||||
while cursor.goto_first_child_for_byte(byte_offset).is_some() {
|
||||
max_depth += 1;
|
||||
}
|
||||
let depth = rng.random_range(0..=max_depth);
|
||||
let depth = rng.gen_range(0..=max_depth);
|
||||
for _ in 0..depth {
|
||||
cursor.goto_parent();
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ impl<'a> Pattern<'a> {
|
|||
let pattern_start = cursor.node().start_position();
|
||||
let mut roots = vec![Self::random_pattern_for_node(&mut cursor, rng)];
|
||||
while roots.len() < 5 && cursor.goto_next_sibling() {
|
||||
if rng.random_bool(0.2) {
|
||||
if rng.gen_bool(0.2) {
|
||||
roots.push(Self::random_pattern_for_node(&mut cursor, rng));
|
||||
}
|
||||
}
|
||||
|
|
@ -75,26 +75,26 @@ impl<'a> Pattern<'a> {
|
|||
(pattern, pattern_start..pattern_end)
|
||||
}
|
||||
|
||||
fn random_pattern_for_node(cursor: &mut TreeCursor<'a>, rng: &mut impl Rng) -> Self {
|
||||
fn random_pattern_for_node(cursor: &mut TreeCursor, rng: &mut impl Rng) -> Self {
|
||||
let node = cursor.node();
|
||||
|
||||
// Sometimes specify the node's type, sometimes use a wildcard.
|
||||
let (kind, named) = if rng.random_bool(0.9) {
|
||||
let (kind, named) = if rng.gen_bool(0.9) {
|
||||
(Some(node.kind()), node.is_named())
|
||||
} else {
|
||||
(Some("_"), node.is_named() && rng.random_bool(0.8))
|
||||
(Some("_"), node.is_named() && rng.gen_bool(0.8))
|
||||
};
|
||||
|
||||
// Sometimes specify the node's field.
|
||||
let field = if rng.random_bool(0.75) {
|
||||
let field = if rng.gen_bool(0.75) {
|
||||
cursor.field_name()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Sometimes capture the node.
|
||||
let capture = if rng.random_bool(0.7) {
|
||||
Some(CAPTURE_NAMES[rng.random_range(0..CAPTURE_NAMES.len())].to_string())
|
||||
let capture = if rng.gen_bool(0.7) {
|
||||
Some(CAPTURE_NAMES[rng.gen_range(0..CAPTURE_NAMES.len())].to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
@ -102,9 +102,9 @@ impl<'a> Pattern<'a> {
|
|||
// Walk the children and include child patterns for some of them.
|
||||
let mut children = Vec::new();
|
||||
if named && cursor.goto_first_child() {
|
||||
let max_children = rng.random_range(0..4);
|
||||
let max_children = rng.gen_range(0..4);
|
||||
while cursor.goto_next_sibling() {
|
||||
if rng.random_bool(0.6) {
|
||||
if rng.gen_bool(0.6) {
|
||||
let child_ast = Self::random_pattern_for_node(cursor, rng);
|
||||
children.push(child_ast);
|
||||
if children.len() >= max_children {
|
||||
|
|
@ -204,10 +204,10 @@ impl<'a> Pattern<'a> {
|
|||
}
|
||||
|
||||
// If a field is specified, check that it matches the node.
|
||||
if let Some(field) = self.field
|
||||
&& cursor.field_name() != Some(field)
|
||||
{
|
||||
return Vec::new();
|
||||
if let Some(field) = self.field {
|
||||
if cursor.field_name() != Some(field) {
|
||||
return Vec::new();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a match for the current node.
|
||||
|
|
@ -225,7 +225,7 @@ impl<'a> Pattern<'a> {
|
|||
}
|
||||
|
||||
// Find every matching combination of child patterns and child nodes.
|
||||
let mut finished_matches = Vec::<Match<'_, 'tree>>::new();
|
||||
let mut finished_matches = Vec::<Match>::new();
|
||||
if cursor.goto_first_child() {
|
||||
let mut match_states = vec![(0, mat)];
|
||||
loop {
|
||||
|
|
@ -268,7 +268,7 @@ impl<'a> Pattern<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Pattern<'_> {
|
||||
impl std::fmt::Display for Pattern {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut result = String::new();
|
||||
self.write_to_string(&mut result, 0);
|
||||
|
|
@ -336,7 +336,7 @@ pub fn collect_matches<'a>(
|
|||
while let Some(m) = matches.next() {
|
||||
result.push((
|
||||
m.pattern_index,
|
||||
format_captures(m.captures().iter().into_streaming_iter_ref(), query, source),
|
||||
format_captures(m.captures.iter().into_streaming_iter_ref(), query, source),
|
||||
));
|
||||
}
|
||||
result
|
||||
|
|
@ -347,7 +347,7 @@ pub fn collect_captures<'a>(
|
|||
query: &'a Query,
|
||||
source: &'a str,
|
||||
) -> Vec<(&'a str, &'a str)> {
|
||||
format_captures(captures.map(|(m, i)| m.captures()[*i]), query, source)
|
||||
format_captures(captures.map(|(m, i)| m.captures[*i]), query, source)
|
||||
}
|
||||
|
||||
fn format_captures<'a>(
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@ use std::{
|
|||
ffi::CString,
|
||||
fs,
|
||||
os::raw::c_char,
|
||||
ptr, slice,
|
||||
ptr, slice, str,
|
||||
sync::{
|
||||
LazyLock,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
LazyLock,
|
||||
},
|
||||
};
|
||||
|
||||
use tree_sitter_highlight::{
|
||||
Error, Highlight, HighlightConfiguration, HighlightEvent, Highlighter, HtmlRenderer, c,
|
||||
c, Error, Highlight, HighlightConfiguration, HighlightEvent, Highlighter, HtmlRenderer,
|
||||
};
|
||||
|
||||
use super::helpers::fixtures::{get_highlight_config, get_language, get_language_queries_path};
|
||||
|
|
@ -485,7 +485,6 @@ fn test_highlighting_cancellation() {
|
|||
.highlight(
|
||||
&HTML_HIGHLIGHT,
|
||||
source.as_bytes(),
|
||||
None,
|
||||
Some(&cancellation_flag),
|
||||
injection_callback,
|
||||
)
|
||||
|
|
@ -496,7 +495,7 @@ fn test_highlighting_cancellation() {
|
|||
let found_cancellation_error = events.any(|event| match event {
|
||||
Ok(_) => false,
|
||||
Err(Error::Cancelled) => true,
|
||||
Err(Error::InvalidLanguage(_) | Error::Unknown) => {
|
||||
Err(Error::InvalidLanguage | Error::Unknown) => {
|
||||
unreachable!("Unexpected error type while iterating events")
|
||||
}
|
||||
});
|
||||
|
|
@ -728,7 +727,6 @@ fn to_html<'a>(
|
|||
language_config,
|
||||
src,
|
||||
None,
|
||||
None,
|
||||
&test_language_for_injection_string,
|
||||
)?;
|
||||
|
||||
|
|
@ -749,10 +747,7 @@ fn to_html<'a>(
|
|||
.collect())
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::type_complexity,
|
||||
reason = "return type represents structured highlight tokens"
|
||||
)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn to_token_vector<'a>(
|
||||
src: &'a str,
|
||||
language_config: &'a HighlightConfiguration,
|
||||
|
|
@ -766,7 +761,6 @@ fn to_token_vector<'a>(
|
|||
language_config,
|
||||
src,
|
||||
None,
|
||||
None,
|
||||
&test_language_for_injection_string,
|
||||
)?;
|
||||
for event in events {
|
||||
|
|
|
|||
|
|
@ -31,17 +31,14 @@ fn test_lookahead_iterator() {
|
|||
let mut lookahead = language.lookahead_iterator(next_state).unwrap();
|
||||
assert_eq!(*lookahead.language(), language);
|
||||
assert!(lookahead.iter_names().eq(expected_symbols));
|
||||
assert_eq!(lookahead.iter_names().count(), 0);
|
||||
|
||||
assert!(lookahead.reset_state(next_state));
|
||||
lookahead.reset_state(next_state);
|
||||
assert!(lookahead.iter_names().eq(expected_symbols));
|
||||
|
||||
assert!(lookahead.reset(&language, next_state));
|
||||
assert!(
|
||||
lookahead
|
||||
.map(|s| language.node_kind_for_id(s).unwrap())
|
||||
.eq(expected_symbols)
|
||||
);
|
||||
lookahead.reset(&language, next_state);
|
||||
assert!(lookahead
|
||||
.map(|s| language.node_kind_for_id(s).unwrap())
|
||||
.eq(expected_symbols));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -67,33 +64,6 @@ fn test_lookahead_iterator_modifiable_only_by_mut() {
|
|||
let _ = names.next();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lookahead_iterator_exhaustion() {
|
||||
let language = get_language("json");
|
||||
|
||||
for state in 0..language.parse_state_count() {
|
||||
let state = u16::try_from(state).unwrap();
|
||||
let mut lookahead = language.lookahead_iterator(state).unwrap();
|
||||
|
||||
// A fresh iterator is not positioned on a symbol.
|
||||
assert_eq!(lookahead.current_symbol(), None);
|
||||
assert_eq!(lookahead.current_symbol_name(), None);
|
||||
|
||||
let count = lookahead.by_ref().count();
|
||||
|
||||
// An exhausted iterator is not positioned on a symbol, and stays exhausted.
|
||||
assert_eq!(lookahead.current_symbol(), None);
|
||||
assert_eq!(lookahead.current_symbol_name(), None);
|
||||
assert_eq!(lookahead.by_ref().count(), 0);
|
||||
assert_eq!(lookahead.iter_names().count(), 0);
|
||||
|
||||
// Resetting restores it exactly.
|
||||
assert!(lookahead.reset_state(state));
|
||||
assert_eq!(lookahead.current_symbol(), None);
|
||||
assert_eq!(lookahead.by_ref().count(), count);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_symbol_metadata_checks() {
|
||||
let language = get_language("rust");
|
||||
|
|
@ -140,7 +110,7 @@ fn test_supertypes() {
|
|||
supertypes
|
||||
.iter()
|
||||
.filter_map(|&s| language.node_kind_for_id(s))
|
||||
.map(std::string::ToString::to_string)
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
vec![
|
||||
"_expression",
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ use tree_sitter::{InputEdit, Node, Parser, Point, Tree};
|
|||
use tree_sitter_generate::load_grammar_file;
|
||||
|
||||
use super::{
|
||||
Rand, get_random_edit,
|
||||
get_random_edit,
|
||||
helpers::fixtures::{fixtures_dir, get_language, get_test_language},
|
||||
Rand,
|
||||
};
|
||||
use crate::{
|
||||
parse::perform_edit,
|
||||
|
|
@ -285,10 +286,7 @@ fn test_parent_of_zero_width_node() {
|
|||
|
||||
assert_eq!(block.to_string(), "(block)");
|
||||
assert_eq!(block_parent.kind(), "function_definition");
|
||||
assert_eq!(
|
||||
block_parent.to_string(),
|
||||
"(function_definition name: (identifier) parameters: (parameters (identifier)) body: (block))"
|
||||
);
|
||||
assert_eq!(block_parent.to_string(), "(function_definition name: (identifier) parameters: (parameters (identifier)) body: (block))");
|
||||
|
||||
assert_eq!(
|
||||
root.child_with_descendant(block).unwrap(),
|
||||
|
|
@ -454,7 +452,7 @@ fn test_node_child_by_field_name_with_extra_hidden_children() {
|
|||
// In the Python grammar, some fields are applied to `suite` nodes,
|
||||
// which consist of an invisible `indent` token followed by a block.
|
||||
// Check that when searching for a child with a field name, we don't
|
||||
// return a hidden child node.
|
||||
//
|
||||
let tree = parser.parse("while a:\n pass", None).unwrap();
|
||||
let while_node = tree.root_node().child(0).unwrap();
|
||||
assert_eq!(while_node.kind(), "while_statement");
|
||||
|
|
@ -950,13 +948,6 @@ fn test_node_sexp() {
|
|||
|
||||
#[test]
|
||||
fn test_node_field_names() {
|
||||
// - "x":
|
||||
// This isn't used in the test, but prevents `_hidden_rule1` from being eliminated as a
|
||||
// unit reduction.
|
||||
// - "_hidden_rule1":
|
||||
// Fields pointing to hidden nodes with a single child resolve to the child.
|
||||
// - "_hidden_rule2":
|
||||
// Fields within hidden nodes can be referenced through the parent node.
|
||||
let (parser_name, parser_code) = generate_parser(
|
||||
r#"
|
||||
{
|
||||
|
|
@ -979,6 +970,8 @@ fn test_node_field_names() {
|
|||
{"type": "STRING", "value": "child-1"},
|
||||
{"type": "BLANK"},
|
||||
|
||||
// This isn't used in the test, but prevents `_hidden_rule1`
|
||||
// from being eliminated as a unit reduction.
|
||||
{
|
||||
"type": "ALIAS",
|
||||
"value": "x",
|
||||
|
|
@ -999,6 +992,7 @@ fn test_node_field_names() {
|
|||
]
|
||||
},
|
||||
|
||||
// Fields pointing to hidden nodes with a single child resolve to the child.
|
||||
"_hidden_rule1": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
|
|
@ -1007,6 +1001,7 @@ fn test_node_field_names() {
|
|||
]
|
||||
},
|
||||
|
||||
// Fields within hidden nodes can be referenced through the parent node.
|
||||
"_hidden_rule2": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
|
|
|
|||
|
|
@ -66,13 +66,9 @@ fn test_parsing_with_logging() {
|
|||
parser.set_language(&get_language("rust")).unwrap();
|
||||
|
||||
let mut messages = Vec::new();
|
||||
// SAFETY: the logger borrows `messages` and is only invoked during the
|
||||
// `parse` call below while `messages` is in scope.
|
||||
unsafe {
|
||||
parser.set_logger_unchecked(Some(Box::new(|log_type, message| {
|
||||
messages.push((log_type, message.to_string()));
|
||||
})));
|
||||
}
|
||||
parser.set_logger(Some(Box::new(|log_type, message| {
|
||||
messages.push((log_type, message.to_string()));
|
||||
})));
|
||||
|
||||
parser
|
||||
.parse(
|
||||
|
|
@ -253,21 +249,6 @@ fn test_parsing_with_custom_utf16_be_input() {
|
|||
assert_eq!(root.child(0).unwrap().kind(), "function_item");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_utf16_decodes_surrogate_pairs() {
|
||||
let mut parser = Parser::new();
|
||||
let language = get_test_fixture_language("utf16_surrogate_oob");
|
||||
parser.set_language(&language).unwrap();
|
||||
|
||||
let le = [0xD83D_u16.to_le(), 0xDE00_u16.to_le()];
|
||||
let tree = parser.parse_utf16_le(le, None).unwrap();
|
||||
assert_eq!(tree.root_node().to_sexp(), "(program (supplementary))");
|
||||
|
||||
let be = [0xD83D_u16.to_be(), 0xDE00_u16.to_be()];
|
||||
let tree = parser.parse_utf16_be(be, None).unwrap();
|
||||
assert_eq!(tree.root_node().to_sexp(), "(program (supplementary))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_utf16_decode_does_not_read_oob() {
|
||||
// Test for a buffer over-read in ts_decode_utf16_le/be when a lead surrogate
|
||||
|
|
@ -822,7 +803,11 @@ fn test_parsing_cancelled_by_another_thread() {
|
|||
&mut |offset, _| {
|
||||
thread::yield_now();
|
||||
thread::sleep(time::Duration::from_millis(10));
|
||||
if offset == 0 { b" [" } else { b"0," }
|
||||
if offset == 0 {
|
||||
b" ["
|
||||
} else {
|
||||
b"0,"
|
||||
}
|
||||
},
|
||||
None,
|
||||
Some(ParseOptions::new().progress_callback(callback)),
|
||||
|
|
@ -845,7 +830,11 @@ fn test_parsing_with_a_timeout() {
|
|||
let start_time = time::Instant::now();
|
||||
let tree = parser.parse_with_options(
|
||||
&mut |offset, _| {
|
||||
if offset == 0 { b" [" } else { b",0" }
|
||||
if offset == 0 {
|
||||
b" ["
|
||||
} else {
|
||||
b",0"
|
||||
}
|
||||
},
|
||||
None,
|
||||
Some(ParseOptions::new().progress_callback(&mut |_| {
|
||||
|
|
@ -863,7 +852,11 @@ fn test_parsing_with_a_timeout() {
|
|||
let start_time = time::Instant::now();
|
||||
let tree = parser.parse_with_options(
|
||||
&mut |offset, _| {
|
||||
if offset == 0 { b" [" } else { b",0" }
|
||||
if offset == 0 {
|
||||
b" ["
|
||||
} else {
|
||||
b",0"
|
||||
}
|
||||
},
|
||||
None,
|
||||
Some(ParseOptions::new().progress_callback(&mut |_| {
|
||||
|
|
@ -1060,9 +1053,9 @@ fn test_parsing_with_timeout_during_balancing() {
|
|||
let mut parser = Parser::new();
|
||||
parser.set_language(&get_language("javascript")).unwrap();
|
||||
|
||||
let function_count: u32 = 100;
|
||||
let function_count = 100;
|
||||
|
||||
let code = "function() {}\n".repeat(function_count as usize);
|
||||
let code = "function() {}\n".repeat(function_count);
|
||||
let mut current_byte_offset = 0;
|
||||
let mut in_balancing = false;
|
||||
let tree = parser.parse_with_options(
|
||||
|
|
@ -1804,15 +1797,11 @@ fn test_parsing_with_scanner_logging() {
|
|||
.unwrap();
|
||||
|
||||
let mut found = false;
|
||||
// SAFETY: the logger borrows `found` and is only invoked during the `parse`
|
||||
// call below, while `found` is in scope.
|
||||
unsafe {
|
||||
parser.set_logger_unchecked(Some(Box::new(|log_type, message| {
|
||||
if log_type == LogType::Lex && message == "Found a percent string" {
|
||||
found = true;
|
||||
}
|
||||
})));
|
||||
}
|
||||
parser.set_logger(Some(Box::new(|log_type, message| {
|
||||
if log_type == LogType::Lex && message == "Found a percent string" {
|
||||
found = true;
|
||||
}
|
||||
})));
|
||||
|
||||
let source_code = "x + %(sup (external) scanner?)";
|
||||
|
||||
|
|
@ -1932,7 +1921,7 @@ fn test_decode_cp1252() {
|
|||
fn decode(bytes: &[u8]) -> (i32, u32) {
|
||||
if !bytes.is_empty() {
|
||||
let byte = bytes[0];
|
||||
(i32::from(byte), 1)
|
||||
(byte as i32, 1)
|
||||
} else {
|
||||
(0, 0)
|
||||
}
|
||||
|
|
@ -1968,7 +1957,7 @@ fn test_decode_macintosh() {
|
|||
fn decode(bytes: &[u8]) -> (i32, u32) {
|
||||
if !bytes.is_empty() {
|
||||
let byte = bytes[0];
|
||||
(i32::from(byte), 1)
|
||||
(byte as i32, 1)
|
||||
} else {
|
||||
(0, 0)
|
||||
}
|
||||
|
|
@ -2030,9 +2019,8 @@ fn test_decode_utf24le() {
|
|||
|
||||
#[test]
|
||||
fn test_grammars_that_should_not_compile() {
|
||||
assert!(
|
||||
generate_parser(
|
||||
r#"
|
||||
assert!(generate_parser(
|
||||
r#"
|
||||
{
|
||||
"name": "issue_1111",
|
||||
"rules": {
|
||||
|
|
@ -2040,13 +2028,11 @@ fn test_grammars_that_should_not_compile() {
|
|||
},
|
||||
}
|
||||
"#
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
)
|
||||
.is_err());
|
||||
|
||||
assert!(
|
||||
generate_parser(
|
||||
r#"
|
||||
assert!(generate_parser(
|
||||
r#"
|
||||
{
|
||||
"name": "issue_1271",
|
||||
"rules": {
|
||||
|
|
@ -2061,13 +2047,11 @@ fn test_grammars_that_should_not_compile() {
|
|||
},
|
||||
}
|
||||
"#
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
)
|
||||
.is_err());
|
||||
|
||||
assert!(
|
||||
generate_parser(
|
||||
r#"
|
||||
assert!(generate_parser(
|
||||
r#"
|
||||
{
|
||||
"name": "issue_1156_expl_1",
|
||||
"rules": {
|
||||
|
|
@ -2081,13 +2065,11 @@ fn test_grammars_that_should_not_compile() {
|
|||
},
|
||||
}
|
||||
"#
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
)
|
||||
.is_err());
|
||||
|
||||
assert!(
|
||||
generate_parser(
|
||||
r#"
|
||||
assert!(generate_parser(
|
||||
r#"
|
||||
{
|
||||
"name": "issue_1156_expl_2",
|
||||
"rules": {
|
||||
|
|
@ -2104,13 +2086,11 @@ fn test_grammars_that_should_not_compile() {
|
|||
},
|
||||
}
|
||||
"#
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
)
|
||||
.is_err());
|
||||
|
||||
assert!(
|
||||
generate_parser(
|
||||
r#"
|
||||
assert!(generate_parser(
|
||||
r#"
|
||||
{
|
||||
"name": "issue_1156_expl_3",
|
||||
"rules": {
|
||||
|
|
@ -2124,13 +2104,11 @@ fn test_grammars_that_should_not_compile() {
|
|||
},
|
||||
}
|
||||
"#
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
)
|
||||
.is_err());
|
||||
|
||||
assert!(
|
||||
generate_parser(
|
||||
r#"
|
||||
assert!(generate_parser(
|
||||
r#"
|
||||
{
|
||||
"name": "issue_1156_expl_4",
|
||||
"rules": {
|
||||
|
|
@ -2147,9 +2125,8 @@ fn test_grammars_that_should_not_compile() {
|
|||
},
|
||||
}
|
||||
"#
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
const fn simple_range(start: usize, end: usize) -> Range {
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@ proc-macro = true
|
|||
[dependencies]
|
||||
proc-macro2 = "1.0.93"
|
||||
quote = "1.0.38"
|
||||
syn = { features = [ "full" ], version = "2.0.96" }
|
||||
syn = { version = "2.0.96", features = ["full"] }
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@ use proc_macro::TokenStream;
|
|||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
Error, Expr, Ident, ItemFn, LitInt, Token,
|
||||
parse::{Parse, ParseStream},
|
||||
parse_macro_input,
|
||||
parse_macro_input, Error, Expr, Ident, ItemFn, LitInt, Token,
|
||||
};
|
||||
|
||||
#[proc_macro_attribute]
|
||||
|
|
@ -69,7 +68,7 @@ pub fn test_with_seed(args: TokenStream, input: TokenStream) -> TokenStream {
|
|||
return Err(Error::new(
|
||||
name.span(),
|
||||
format!("Unsupported parameter `{x}`"),
|
||||
));
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use std::{env, fmt::Write, ops::ControlFlow, sync::LazyLock};
|
||||
|
||||
use indoc::indoc;
|
||||
use rand::{SeedableRng, prelude::StdRng};
|
||||
use rand::{prelude::StdRng, SeedableRng};
|
||||
use streaming_iterator::StreamingIterator;
|
||||
use tree_sitter::{
|
||||
CaptureQuantifier, InputEdit, Language, Node, Parser, Point, Query, QueryCursor,
|
||||
QueryCursorOptions, QueryCursorState, QueryError, QueryErrorKind, QueryPredicate,
|
||||
QueryPredicateArg, QueryProperty, Range,
|
||||
QueryCursorOptions, QueryError, QueryErrorKind, QueryPredicate, QueryPredicateArg,
|
||||
QueryProperty, Range,
|
||||
};
|
||||
use tree_sitter_generate::load_grammar_file;
|
||||
use unindent::Unindent;
|
||||
|
|
@ -14,14 +14,15 @@ use unindent::Unindent;
|
|||
use super::helpers::{
|
||||
allocations,
|
||||
fixtures::{get_language, get_test_language},
|
||||
query_helpers::{Match, Pattern, assert_query_matches},
|
||||
query_helpers::{assert_query_matches, Match, Pattern},
|
||||
};
|
||||
use crate::tests::{
|
||||
ITERATION_COUNT, generate_parser,
|
||||
generate_parser,
|
||||
helpers::{
|
||||
fixtures::get_test_fixture_language,
|
||||
query_helpers::{collect_captures, collect_matches},
|
||||
},
|
||||
ITERATION_COUNT,
|
||||
};
|
||||
|
||||
static EXAMPLE_FILTER: LazyLock<Option<String>> =
|
||||
|
|
@ -33,13 +34,11 @@ fn test_query_errors_on_invalid_syntax() {
|
|||
let language = get_language("javascript");
|
||||
|
||||
assert!(Query::new(&language, "(if_statement)").is_ok());
|
||||
assert!(
|
||||
Query::new(
|
||||
&language,
|
||||
"(if_statement condition:(parenthesized_expression (identifier)))"
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
assert!(Query::new(
|
||||
&language,
|
||||
"(if_statement condition:(parenthesized_expression (identifier)))"
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
// Mismatched parens
|
||||
assert_eq!(
|
||||
|
|
@ -1163,8 +1162,8 @@ fn test_query_matches_with_immediate_siblings() {
|
|||
// siblings before that child node.
|
||||
// 2. After the last child node in a pattern, it means that there cannot be any named
|
||||
// sibling after that child node.
|
||||
// 3. Between two child nodes in a pattern, it specifies that there cannot be any named
|
||||
// siblings between those two child nodes.
|
||||
// 2. Between two child nodes in a pattern, it specifies that there cannot be any named
|
||||
// siblings between those two child snodes.
|
||||
let query = Query::new(
|
||||
&language,
|
||||
"
|
||||
|
|
@ -2370,7 +2369,7 @@ fn test_query_matches_with_too_many_permutations_to_track() {
|
|||
let matches = cursor.matches(&query, tree.root_node(), source.as_bytes());
|
||||
|
||||
// For this pathological query, some match permutations will be dropped.
|
||||
// Just check that a subset of the results are returned, and no crash or
|
||||
// Just check that a subset of the results are returned, and crash or
|
||||
// leak occurs.
|
||||
assert_eq!(
|
||||
collect_matches(matches, &query, source.as_str())[0],
|
||||
|
|
@ -2597,7 +2596,7 @@ fn test_query_matches_with_supertypes() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[expect(clippy::reversed_empty_ranges, reason = "testing empty range behavior")]
|
||||
#[allow(clippy::reversed_empty_ranges)]
|
||||
fn test_query_matches_within_byte_range() {
|
||||
allocations::record(|| {
|
||||
let language = get_language("javascript");
|
||||
|
|
@ -2918,7 +2917,7 @@ fn test_query_matches_with_wildcard_at_root_intersecting_byte_range() {
|
|||
);
|
||||
|
||||
while let Some(mat) = match_iter.next() {
|
||||
if let Some(capture) = mat.captures().first() {
|
||||
if let Some(capture) = mat.captures.first() {
|
||||
matches.push(capture.node.kind());
|
||||
}
|
||||
}
|
||||
|
|
@ -2934,7 +2933,7 @@ fn test_query_matches_with_wildcard_at_root_intersecting_byte_range() {
|
|||
);
|
||||
|
||||
while let Some(mat) = match_iter.next() {
|
||||
if let Some(capture) = mat.captures().first() {
|
||||
if let Some(capture) = mat.captures.first() {
|
||||
matches.push(capture.node.kind());
|
||||
}
|
||||
}
|
||||
|
|
@ -2950,7 +2949,7 @@ fn test_query_matches_with_wildcard_at_root_intersecting_byte_range() {
|
|||
);
|
||||
|
||||
while let Some(mat) = match_iter.next() {
|
||||
if let Some(capture) = mat.captures().first() {
|
||||
if let Some(capture) = mat.captures.first() {
|
||||
matches.push(capture.node.kind());
|
||||
}
|
||||
}
|
||||
|
|
@ -3008,7 +3007,7 @@ fn test_query_captures_within_byte_range_assigned_after_iterating() {
|
|||
let mut results = Vec::new();
|
||||
let mut first_five = captures.by_ref().take(5);
|
||||
while let Some((mat, capture_ix)) = first_five.next() {
|
||||
let capture = mat.captures()[*capture_ix];
|
||||
let capture = mat.captures[*capture_ix];
|
||||
results.push((
|
||||
query.capture_names()[capture.index as usize],
|
||||
&source[capture.node.byte_range()],
|
||||
|
|
@ -3031,7 +3030,7 @@ fn test_query_captures_within_byte_range_assigned_after_iterating() {
|
|||
results.clear();
|
||||
captures.set_byte_range(source.find("Ok").unwrap()..source.len());
|
||||
while let Some((mat, capture_ix)) = captures.next() {
|
||||
let capture = mat.captures()[*capture_ix];
|
||||
let capture = mat.captures[*capture_ix];
|
||||
results.push((
|
||||
query.capture_names()[capture.index as usize],
|
||||
&source[capture.node.byte_range()],
|
||||
|
|
@ -3330,7 +3329,7 @@ fn test_query_matches_with_captured_wildcard_at_root() {
|
|||
|
||||
while let Some(m) = match_iter.next() {
|
||||
let captures = m
|
||||
.captures()
|
||||
.captures
|
||||
.iter()
|
||||
.map(|c| {
|
||||
(
|
||||
|
|
@ -4311,7 +4310,7 @@ fn test_query_captures_with_matches_removed() {
|
|||
|
||||
let mut captures = cursor.captures(&query, tree.root_node(), source.as_bytes());
|
||||
while let Some((m, i)) = captures.next() {
|
||||
let capture = m.captures()[*i];
|
||||
let capture = m.captures[*i];
|
||||
let text = capture.node.utf8_text(source.as_bytes()).unwrap();
|
||||
if text == "a" {
|
||||
m.remove();
|
||||
|
|
@ -4356,7 +4355,7 @@ fn test_query_captures_with_matches_removed_before_they_finish() {
|
|||
let mut captured_strings = Vec::new();
|
||||
let mut captures = cursor.captures(&query, tree.root_node(), source.as_bytes());
|
||||
while let Some((m, i)) = captures.next() {
|
||||
let capture = m.captures()[*i];
|
||||
let capture = m.captures[*i];
|
||||
let text = capture.node.utf8_text(source.as_bytes()).unwrap();
|
||||
if text == "as" {
|
||||
m.remove();
|
||||
|
|
@ -4397,18 +4396,18 @@ fn test_query_captures_and_matches_iterators_are_fused() {
|
|||
let mut cursor = QueryCursor::new();
|
||||
let mut captures = cursor.captures(&query, tree.root_node(), source.as_bytes());
|
||||
|
||||
assert_eq!(captures.next().unwrap().0.captures()[0].index, 0);
|
||||
assert_eq!(captures.next().unwrap().0.captures()[0].index, 0);
|
||||
assert_eq!(captures.next().unwrap().0.captures()[0].index, 0);
|
||||
assert_eq!(captures.next().unwrap().0.captures[0].index, 0);
|
||||
assert_eq!(captures.next().unwrap().0.captures[0].index, 0);
|
||||
assert_eq!(captures.next().unwrap().0.captures[0].index, 0);
|
||||
assert!(captures.next().is_none());
|
||||
assert!(captures.next().is_none());
|
||||
assert!(captures.next().is_none());
|
||||
drop(captures);
|
||||
|
||||
let mut matches = cursor.matches(&query, tree.root_node(), source.as_bytes());
|
||||
assert_eq!(matches.next().unwrap().captures()[0].index, 0);
|
||||
assert_eq!(matches.next().unwrap().captures()[0].index, 0);
|
||||
assert_eq!(matches.next().unwrap().captures()[0].index, 0);
|
||||
assert_eq!(matches.next().unwrap().captures[0].index, 0);
|
||||
assert_eq!(matches.next().unwrap().captures[0].index, 0);
|
||||
assert_eq!(matches.next().unwrap().captures[0].index, 0);
|
||||
assert!(matches.next().is_none());
|
||||
assert!(matches.next().is_none());
|
||||
assert!(matches.next().is_none());
|
||||
|
|
@ -4583,13 +4582,13 @@ fn test_query_lifetime_is_separate_from_nodes_lifetime() {
|
|||
let language = get_language("javascript");
|
||||
let query = Query::new(&language, query).unwrap();
|
||||
let mut cursor = QueryCursor::new();
|
||||
|
||||
cursor
|
||||
let node = cursor
|
||||
.matches(&query, node, source.as_bytes())
|
||||
.next()
|
||||
.unwrap()
|
||||
.captures()[0]
|
||||
.node
|
||||
.captures[0]
|
||||
.node;
|
||||
node
|
||||
}
|
||||
|
||||
let node = take_first_node_from_captures(source, query, tree.root_node());
|
||||
|
|
@ -4603,14 +4602,14 @@ fn test_query_lifetime_is_separate_from_nodes_lifetime() {
|
|||
let language = get_language("javascript");
|
||||
let query = Query::new(&language, query).unwrap();
|
||||
let mut cursor = QueryCursor::new();
|
||||
|
||||
cursor
|
||||
let node = cursor
|
||||
.captures(&query, node, source.as_bytes())
|
||||
.next()
|
||||
.unwrap()
|
||||
.0
|
||||
.captures()[0]
|
||||
.node
|
||||
.captures[0]
|
||||
.node;
|
||||
node
|
||||
}
|
||||
|
||||
let node = take_first_node_from_matches(source, query, tree.root_node());
|
||||
|
|
@ -4623,7 +4622,7 @@ fn test_query_with_no_patterns() {
|
|||
allocations::record(|| {
|
||||
let language = get_language("javascript");
|
||||
let query = Query::new(&language, "").unwrap();
|
||||
assert_eq!(query.capture_names(), [] as [&str; 0]);
|
||||
assert!(query.capture_names().is_empty());
|
||||
assert_eq!(query.pattern_count(), 0);
|
||||
});
|
||||
}
|
||||
|
|
@ -4696,54 +4695,6 @@ fn test_query_disable_pattern() {
|
|||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_deep_clone() {
|
||||
allocations::record(|| {
|
||||
let language = get_language("javascript");
|
||||
let query = Query::new(
|
||||
&language,
|
||||
"
|
||||
(function_declaration
|
||||
name: (identifier) @name)
|
||||
(function_declaration
|
||||
body: (statement_block) @body)
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut clone = query.deep_clone();
|
||||
clone.disable_pattern(1);
|
||||
|
||||
let source = "function foo() { return 1; }";
|
||||
let mut parser = Parser::new();
|
||||
parser.set_language(&language).unwrap();
|
||||
let tree = parser.parse(source, None).unwrap();
|
||||
let mut cursor = QueryCursor::new();
|
||||
|
||||
// The clone with pattern 1 disabled only produces the @name match.
|
||||
let clone_matches = collect_matches(
|
||||
cursor.matches(&clone, tree.root_node(), source.as_bytes()),
|
||||
&clone,
|
||||
source,
|
||||
);
|
||||
assert_eq!(clone_matches, &[(0, vec![("name", "foo")])]);
|
||||
|
||||
// The original is unaffected and still produces both @name and @body.
|
||||
let original_matches = collect_matches(
|
||||
cursor.matches(&query, tree.root_node(), source.as_bytes()),
|
||||
&query,
|
||||
source,
|
||||
);
|
||||
assert_eq!(
|
||||
original_matches,
|
||||
&[
|
||||
(0, vec![("name", "foo")]),
|
||||
(1, vec![("body", "{ return 1; }")]),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_alternative_predicate_prefix() {
|
||||
allocations::record(|| {
|
||||
|
|
@ -4819,7 +4770,7 @@ fn test_query_random() {
|
|||
let transformed_match = Match {
|
||||
last_node: None,
|
||||
captures: mat
|
||||
.captures()
|
||||
.captures
|
||||
.iter()
|
||||
.map(|c| (query.capture_names()[c.index as usize], c.node))
|
||||
.collect::<Vec<_>>(),
|
||||
|
|
@ -5122,10 +5073,10 @@ fn test_query_is_pattern_guaranteed_at_step() {
|
|||
eprintln!();
|
||||
|
||||
for row in rows {
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref()
|
||||
&& !row.description.contains(filter.as_str())
|
||||
{
|
||||
continue;
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref() {
|
||||
if !row.description.contains(filter.as_str()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
eprintln!(" query example: {:?}", row.description);
|
||||
let query = Query::new(&row.language, row.pattern).unwrap();
|
||||
|
|
@ -5218,10 +5169,10 @@ fn test_query_is_pattern_rooted() {
|
|||
|
||||
let language = get_language("python");
|
||||
for row in &rows {
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref()
|
||||
&& !row.description.contains(filter.as_str())
|
||||
{
|
||||
continue;
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref() {
|
||||
if !row.description.contains(filter.as_str()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
eprintln!(" query example: {:?}", row.description);
|
||||
let query = Query::new(&language, row.pattern).unwrap();
|
||||
|
|
@ -5315,10 +5266,10 @@ fn test_query_is_pattern_non_local() {
|
|||
eprintln!();
|
||||
|
||||
for row in &rows {
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref()
|
||||
&& !row.description.contains(filter.as_str())
|
||||
{
|
||||
continue;
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref() {
|
||||
if !row.description.contains(filter.as_str()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
eprintln!(" query example: {:?}", row.description);
|
||||
let query = Query::new(&row.language, row.pattern).unwrap();
|
||||
|
|
@ -5545,10 +5496,10 @@ fn test_capture_quantifiers() {
|
|||
eprintln!();
|
||||
|
||||
for row in rows {
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref()
|
||||
&& !row.description.contains(filter.as_str())
|
||||
{
|
||||
continue;
|
||||
if let Some(filter) = EXAMPLE_FILTER.as_ref() {
|
||||
if !row.description.contains(filter.as_str()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
eprintln!(" query example: {:?}", row.description);
|
||||
let query = Query::new(&row.language, row.pattern).unwrap();
|
||||
|
|
@ -5833,10 +5784,10 @@ fn test_consecutive_zero_or_modifiers() {
|
|||
let mut len_1 = false;
|
||||
|
||||
while let Some(m) = matches.next() {
|
||||
if m.captures().len() == 3 {
|
||||
if m.captures.len() == 3 {
|
||||
len_3 = true;
|
||||
}
|
||||
if m.captures().len() == 1 {
|
||||
if m.captures.len() == 1 {
|
||||
len_1 = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -5898,7 +5849,7 @@ fn test_query_max_start_depth_more() {
|
|||
let query = Query::new(&language, "(compound_statement) @capture").unwrap();
|
||||
|
||||
let mut matches = cursor.matches(&query, tree.root_node(), source.as_bytes());
|
||||
let node = matches.next().unwrap().captures()[0].node;
|
||||
let node = matches.next().unwrap().captures[0].node;
|
||||
assert_eq!(node.kind(), "compound_statement");
|
||||
|
||||
for row in rows {
|
||||
|
|
@ -6074,21 +6025,21 @@ fn test_query_execution_with_timeout() {
|
|||
let tree = parser.parse(&source_code, None).unwrap();
|
||||
|
||||
let query = Query::new(&language, "(function_declaration) @function").unwrap();
|
||||
let start_time = std::time::Instant::now();
|
||||
let mut progress_callback = |_: &QueryCursorState| {
|
||||
if start_time.elapsed().as_micros() > 1000 {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
};
|
||||
let mut cursor = QueryCursor::new();
|
||||
|
||||
let start_time = std::time::Instant::now();
|
||||
let matches = cursor
|
||||
.matches_with_options(
|
||||
&query,
|
||||
tree.root_node(),
|
||||
source_code.as_bytes(),
|
||||
QueryCursorOptions::new().progress_callback(&mut progress_callback),
|
||||
QueryCursorOptions::new().progress_callback(&mut |_| {
|
||||
if start_time.elapsed().as_micros() > 1000 {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}),
|
||||
)
|
||||
.count();
|
||||
assert!(matches < 1000);
|
||||
|
|
@ -6099,43 +6050,13 @@ fn test_query_execution_with_timeout() {
|
|||
assert_eq!(matches, 1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_progress_callback_lives_as_long_as_matches() {
|
||||
let language = get_language("javascript");
|
||||
let mut parser = Parser::new();
|
||||
parser.set_language(&language).unwrap();
|
||||
|
||||
let source_code = "function foo() {}\n".repeat(1000);
|
||||
let tree = parser.parse(&source_code, None).unwrap();
|
||||
let query = Query::new(&language, "(function_declaration) @function").unwrap();
|
||||
|
||||
let mut cursor = QueryCursor::new();
|
||||
let mut callback_was_called = false;
|
||||
let mut progress_callback = |_: &QueryCursorState| {
|
||||
callback_was_called = true;
|
||||
ControlFlow::Continue(())
|
||||
};
|
||||
let matches = cursor.matches_with_options(
|
||||
&query,
|
||||
tree.root_node(),
|
||||
source_code.as_bytes(),
|
||||
QueryCursorOptions::new().progress_callback(&mut progress_callback),
|
||||
);
|
||||
|
||||
assert_eq!(matches.count(), 1000);
|
||||
assert!(callback_was_called);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_execution_with_points_causing_underflow() {
|
||||
let language = get_language("rust");
|
||||
let mut parser = Parser::new();
|
||||
parser.set_language(&language).unwrap();
|
||||
|
||||
#[expect(
|
||||
clippy::literal_string_with_formatting_args,
|
||||
reason = "raw string contains format syntax as literal code"
|
||||
)]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
let code = r#"fn main() {
|
||||
println!("{:?}", foo());
|
||||
}"#;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
fs, ptr, slice,
|
||||
fs, ptr, slice, str,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
use tree_sitter::Point;
|
||||
use tree_sitter_tags::{Error, TagsConfiguration, TagsContext, c_lib as c};
|
||||
use tree_sitter_tags::{c_lib as c, Error, TagsConfiguration, TagsContext};
|
||||
|
||||
use super::helpers::{
|
||||
allocations,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ use tree_sitter_highlight::{Highlight, Highlighter};
|
|||
|
||||
use super::helpers::fixtures::{get_highlight_config, get_language, test_loader};
|
||||
use crate::{
|
||||
query_testing::{Assertion, Utf8Point, parse_position_comments},
|
||||
test_highlight::{Failure, get_highlight_positions, iterate_assertions},
|
||||
query_testing::{parse_position_comments, Assertion, Utf8Point},
|
||||
test_highlight::{get_highlight_positions, iterate_assertions, Failure},
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use tree_sitter_tags::TagsContext;
|
|||
|
||||
use super::helpers::fixtures::{get_language, get_tags_config};
|
||||
use crate::{
|
||||
query_testing::{Assertion, Utf8Point, parse_position_comments},
|
||||
query_testing::{parse_position_comments, Assertion, Utf8Point},
|
||||
test_tags::get_tag_positions,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ fn tree_query<I: AsRef<[u8]>>(tree: &Tree, text: impl TextProvider<I>, language:
|
|||
let mut cursor = QueryCursor::new();
|
||||
let mut captures = cursor.captures(&query, tree.root_node(), text);
|
||||
let (match_, idx) = captures.next().unwrap();
|
||||
let capture = match_.captures()[*idx];
|
||||
let capture = match_.captures[*idx];
|
||||
assert_eq!(capture.index as usize, *idx);
|
||||
assert_eq!("comment", capture.node.kind());
|
||||
}
|
||||
|
|
@ -126,11 +126,9 @@ fn test_text_provider_callback_with_str_slice() {
|
|||
check_parsing(text, |_node: Node<'_>| iter::once(text));
|
||||
check_parsing_callback(
|
||||
&mut |offset, _point| {
|
||||
if offset < text.len() {
|
||||
text.as_bytes()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
(offset < text.len())
|
||||
.then_some(text.as_bytes())
|
||||
.unwrap_or_default()
|
||||
},
|
||||
|_node: Node<'_>| iter::once(text),
|
||||
);
|
||||
|
|
@ -142,11 +140,9 @@ fn test_text_provider_callback_with_owned_string_slice() {
|
|||
|
||||
check_parsing_callback(
|
||||
&mut |offset, _point| {
|
||||
if offset < text.len() {
|
||||
text.as_bytes()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
(offset < text.len())
|
||||
.then_some(text.as_bytes())
|
||||
.unwrap_or_default()
|
||||
},
|
||||
|_node: Node<'_>| {
|
||||
let slice: String = text.to_owned();
|
||||
|
|
@ -161,11 +157,9 @@ fn test_text_provider_callback_with_owned_bytes_vec_slice() {
|
|||
|
||||
check_parsing_callback(
|
||||
&mut |offset, _point| {
|
||||
if offset < text.len() {
|
||||
text.as_bytes()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
(offset < text.len())
|
||||
.then_some(text.as_bytes())
|
||||
.unwrap_or_default()
|
||||
},
|
||||
|_node: Node<'_>| {
|
||||
let slice = text.to_owned().into_bytes();
|
||||
|
|
@ -180,11 +174,9 @@ fn test_text_provider_callback_with_owned_arc_of_bytes_slice() {
|
|||
|
||||
check_parsing_callback(
|
||||
&mut |offset, _point| {
|
||||
if offset < text.len() {
|
||||
text.as_bytes()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
(offset < text.len())
|
||||
.then_some(text.as_bytes())
|
||||
.unwrap_or_default()
|
||||
},
|
||||
|_node: Node<'_>| {
|
||||
let slice: Arc<[u8]> = text.to_owned().into_bytes().into();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::str;
|
||||
|
||||
use tree_sitter::{InputEdit, Parser, Point, Range, Tree};
|
||||
|
||||
use super::helpers::fixtures::get_language;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use tree_sitter::{Parser, Query, QueryCursor, WasmError, WasmErrorKind, WasmStor
|
|||
|
||||
use crate::tests::helpers::{
|
||||
allocations,
|
||||
fixtures::{ENGINE, WASM_DIR, get_test_fixture_language_wasm},
|
||||
fixtures::{get_test_fixture_language_wasm, ENGINE, WASM_DIR},
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
|
@ -73,10 +73,7 @@ fn test_load_wasm_rust_language() {
|
|||
parser.set_wasm_store(store).unwrap();
|
||||
parser.set_language(&language).unwrap();
|
||||
let tree = parser.parse("fn main() {}", None).unwrap();
|
||||
assert_eq!(
|
||||
tree.root_node().to_sexp(),
|
||||
"(source_file (function_item name: (identifier) parameters: (parameters) body: (block)))"
|
||||
);
|
||||
assert_eq!(tree.root_node().to_sexp(), "(source_file (function_item name: (identifier) parameters: (parameters) body: (block)))");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -90,10 +87,7 @@ fn test_load_wasm_javascript_language() {
|
|||
parser.set_wasm_store(store).unwrap();
|
||||
parser.set_language(&language).unwrap();
|
||||
let tree = parser.parse("const a = b\nconst c = d", None).unwrap();
|
||||
assert_eq!(
|
||||
tree.root_node().to_sexp(),
|
||||
"(program (lexical_declaration (variable_declarator name: (identifier) value: (identifier))) (lexical_declaration (variable_declarator name: (identifier) value: (identifier))))"
|
||||
);
|
||||
assert_eq!(tree.root_node().to_sexp(), "(program (lexical_declaration (variable_declarator name: (identifier) value: (identifier))) (lexical_declaration (variable_declarator name: (identifier) value: (identifier))))");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -107,10 +101,7 @@ fn test_load_wasm_python_language() {
|
|||
parser.set_wasm_store(store).unwrap();
|
||||
parser.set_language(&language).unwrap();
|
||||
let tree = parser.parse("a = b\nc = d", None).unwrap();
|
||||
assert_eq!(
|
||||
tree.root_node().to_sexp(),
|
||||
"(module (expression_statement (assignment left: (identifier) right: (identifier))) (expression_statement (assignment left: (identifier) right: (identifier))))"
|
||||
);
|
||||
assert_eq!(tree.root_node().to_sexp(), "(module (expression_statement (assignment left: (identifier) right: (identifier))) (expression_statement (assignment left: (identifier) right: (identifier))))");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -267,18 +258,12 @@ fn test_reset_wasm_store() {
|
|||
parser.set_wasm_store(parser_store).unwrap();
|
||||
parser.set_language(&language).unwrap();
|
||||
let tree = parser.parse("fn main() {}", None).unwrap();
|
||||
assert_eq!(
|
||||
tree.root_node().to_sexp(),
|
||||
"(source_file (function_item name: (identifier) parameters: (parameters) body: (block)))"
|
||||
);
|
||||
assert_eq!(tree.root_node().to_sexp(), "(source_file (function_item name: (identifier) parameters: (parameters) body: (block)))");
|
||||
|
||||
let parser_store = WasmStore::new(&ENGINE).unwrap();
|
||||
parser.set_wasm_store(parser_store).unwrap();
|
||||
let tree = parser.parse("fn main() {}", None).unwrap();
|
||||
assert_eq!(
|
||||
tree.root_node().to_sexp(),
|
||||
"(source_file (function_item name: (identifier) parameters: (parameters) body: (block)))"
|
||||
);
|
||||
assert_eq!(tree.root_node().to_sexp(), "(source_file (function_item name: (identifier) parameters: (parameters) body: (block)))");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -387,18 +372,3 @@ fn test_wasm_oom() {
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lookahead_iterator_outlives_wasm_language() {
|
||||
allocations::record(|| {
|
||||
let mut store = WasmStore::new(&ENGINE).unwrap();
|
||||
let wasm = fs::read(WASM_DIR.join("tree-sitter-ruby.wasm")).unwrap();
|
||||
let language = store.load_language("ruby", &wasm).unwrap();
|
||||
|
||||
let mut lookahead = language.lookahead_iterator(0).unwrap();
|
||||
drop(language);
|
||||
|
||||
// The iterator retains the language, so the names are still live.
|
||||
assert!(lookahead.iter_names().count() > 0);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ pub mod highlight;
|
|||
pub mod init;
|
||||
pub mod input;
|
||||
pub mod logger;
|
||||
pub mod paint;
|
||||
pub mod parse;
|
||||
pub mod playground;
|
||||
pub mod query;
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
process::{Child, ChildStdin, Command, Stdio},
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use indoc::indoc;
|
||||
use log::error;
|
||||
use tree_sitter::{Parser, Tree};
|
||||
|
|
@ -71,8 +71,8 @@ pub struct LogSession {
|
|||
open_log: bool,
|
||||
}
|
||||
|
||||
pub fn print_tree_graph(tree: &Tree, path: &str, open_log: bool) -> Result<()> {
|
||||
let session = LogSession::new(path, open_log)?;
|
||||
pub fn print_tree_graph(tree: &Tree, path: &str, quiet: bool) -> Result<()> {
|
||||
let session = LogSession::new(path, quiet)?;
|
||||
tree.print_dot_graph(session.dot_process_stdin.as_ref().unwrap());
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -94,9 +94,9 @@ impl LogSession {
|
|||
.stdin(Stdio::piped())
|
||||
.stdout(dot_file)
|
||||
.spawn()
|
||||
.with_context(
|
||||
|| "Failed to run the `dot` command. Check that graphviz is installed.",
|
||||
)?;
|
||||
.with_context(|| {
|
||||
"Failed to run the `dot` command. Check that graphviz is installed."
|
||||
})?;
|
||||
let dot_stdin = dot_process
|
||||
.stdin
|
||||
.take()
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ impl Version {
|
|||
}
|
||||
}
|
||||
|
||||
fn update_file_with<F>(path: &PathBuf, update_fn: F) -> Result<(), UpdateError>
|
||||
fn update_file_with<F>(&self, path: &PathBuf, update_fn: F) -> Result<(), UpdateError>
|
||||
where
|
||||
F: Fn(&str) -> String,
|
||||
{
|
||||
|
|
@ -155,7 +155,7 @@ impl Version {
|
|||
|
||||
fn update_treesitter_json(&self) -> Result<(), UpdateError> {
|
||||
let json_path = self.current_dir.join("tree-sitter.json");
|
||||
Self::update_file_with(&json_path, |content| {
|
||||
self.update_file_with(&json_path, |content| {
|
||||
content
|
||||
.lines()
|
||||
.map(|line| {
|
||||
|
|
@ -189,7 +189,7 @@ impl Version {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
Self::update_file_with(&cargo_toml_path, |content| {
|
||||
self.update_file_with(&cargo_toml_path, |content| {
|
||||
content
|
||||
.lines()
|
||||
.map(|line| {
|
||||
|
|
@ -236,7 +236,7 @@ impl Version {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
Self::update_file_with(&package_json_path, |content| {
|
||||
self.update_file_with(&package_json_path, |content| {
|
||||
content
|
||||
.lines()
|
||||
.map(|line| {
|
||||
|
|
@ -296,7 +296,7 @@ impl Version {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
Self::update_file_with(&makefile_path, |content| {
|
||||
self.update_file_with(&makefile_path, |content| {
|
||||
content
|
||||
.lines()
|
||||
.map(|line| {
|
||||
|
|
@ -320,7 +320,7 @@ impl Version {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
Self::update_file_with(&cmake_lists_path, |content| {
|
||||
self.update_file_with(&cmake_lists_path, |content| {
|
||||
let re = Regex::new(r#"(\s*VERSION\s+)"[0-9]+\.[0-9]+\.[0-9]+""#)
|
||||
.expect("Failed to compile regex");
|
||||
re.replace(
|
||||
|
|
@ -339,7 +339,7 @@ impl Version {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
Self::update_file_with(&pyproject_toml_path, |content| {
|
||||
self.update_file_with(&pyproject_toml_path, |content| {
|
||||
content
|
||||
.lines()
|
||||
.map(|line| {
|
||||
|
|
@ -363,7 +363,7 @@ impl Version {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
Self::update_file_with(&zig_zon_path, |content| {
|
||||
self.update_file_with(&zig_zon_path, |content| {
|
||||
let zig_version_prefix = ".version =";
|
||||
content
|
||||
.lines()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use tree_sitter::wasm_stdlib_symbols;
|
||||
use tree_sitter_generate::{load_grammar_file, parse_grammar::GrammarJSON};
|
||||
use tree_sitter_loader::Loader;
|
||||
|
|
@ -83,17 +83,14 @@ pub fn compile_language_to_wasm(
|
|||
let wasm_bytes = fs::read(&output_filename)?;
|
||||
let parser = Parser::new(0);
|
||||
for payload in parser.parse_all(&wasm_bytes) {
|
||||
if let wasmparser::Payload::ImportSection(reader) = payload? {
|
||||
for imports in reader {
|
||||
for import in imports? {
|
||||
let (_, import) = import?;
|
||||
let name = import.name;
|
||||
if !builtin_symbols.contains(&name)
|
||||
&& !stdlib_symbols.contains(&name)
|
||||
&& !dylink_symbols.contains(&name)
|
||||
{
|
||||
missing_symbols.push(name);
|
||||
}
|
||||
if let wasmparser::Payload::ImportSection(imports) = payload? {
|
||||
for import in imports {
|
||||
let import = import?.name;
|
||||
if !builtin_symbols.contains(&import)
|
||||
&& !stdlib_symbols.contains(&import)
|
||||
&& !dylink_symbols.contains(&import)
|
||||
{
|
||||
missing_symbols.push(import);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ description = "User configuration of tree-sitter's command line programs"
|
|||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme = "README.md"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/tree-sitter-config"
|
||||
|
|
|
|||
|
|
@ -28,24 +28,14 @@ pub enum ConfigError {
|
|||
#[derive(Debug, Error)]
|
||||
pub struct IoError {
|
||||
pub error: std::io::Error,
|
||||
pub path: Option<PathBuf>,
|
||||
pub path: Option<String>,
|
||||
}
|
||||
|
||||
impl PartialEq for IoError {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.path == other.path
|
||||
&& self.error.kind() == other.error.kind()
|
||||
&& self.error.raw_os_error() == other.error.raw_os_error()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for IoError {}
|
||||
|
||||
impl IoError {
|
||||
fn new(error: std::io::Error, path: Option<&Path>) -> Self {
|
||||
Self {
|
||||
error,
|
||||
path: path.map(Path::to_path_buf),
|
||||
path: path.map(|p| p.to_string_lossy().to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +44,7 @@ impl std::fmt::Display for IoError {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.error)?;
|
||||
if let Some(ref path) = self.path {
|
||||
write!(f, " ({})", path.display())?;
|
||||
write!(f, " ({path})")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ description = "Library for generating C source code from a tree-sitter grammar"
|
|||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme = "README.md"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
documentation = "https://docs.rs/tree-sitter-generate"
|
||||
|
|
@ -19,24 +20,30 @@ path = "src/generate.rs"
|
|||
workspace = true
|
||||
|
||||
[features]
|
||||
default = [ "qjs-rt" ]
|
||||
load = [ "dep:semver" ]
|
||||
qjs-rt = [ "load", "rquickjs", "pathdiff" ]
|
||||
default = ["qjs-rt"]
|
||||
load = ["dep:semver"]
|
||||
qjs-rt = ["load", "rquickjs", "pathdiff"]
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.11.1"
|
||||
bitflags = "2.9.4"
|
||||
dunce = "1.0.5"
|
||||
hashbrown.workspace = true
|
||||
indexmap.workspace = true
|
||||
indoc.workspace = true
|
||||
log.workspace = true
|
||||
pathdiff = { optional = true, version = "0.2.3" }
|
||||
pathdiff = { version = "0.2.3", optional = true }
|
||||
regex.workspace = true
|
||||
regex-syntax.workspace = true
|
||||
rquickjs = { features = [ "bindgen", "loader", "macro", "phf" ], optional = true, version = "0.13" }
|
||||
rquickjs = { version = "0.10.0", optional = true, features = [
|
||||
"bindgen",
|
||||
"loader",
|
||||
"macro",
|
||||
"phf",
|
||||
] }
|
||||
rustc-hash.workspace = true
|
||||
semver = { optional = true, workspace = true }
|
||||
semver = { workspace = true, optional = true }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
smallbitvec.workspace = true
|
||||
thiserror.workspace = true
|
||||
topological-sort.workspace = true
|
||||
|
||||
|
|
|
|||
|
|
@ -1,415 +0,0 @@
|
|||
use std::{
|
||||
hash::{Hash, Hasher},
|
||||
ptr,
|
||||
};
|
||||
|
||||
const ARENA_CHUNK_WORDS: usize = 128 * 1024 / std::mem::size_of::<u64>();
|
||||
|
||||
struct WordArena {
|
||||
chunks: Vec<Vec<u64>>,
|
||||
offset: usize,
|
||||
/// Freed blocks grouped by size (number of words). Checked before
|
||||
/// bump-allocating so that dropped `BitVec`s can be reused immediately.
|
||||
free_list: Vec<(usize, Vec<*mut u64>)>,
|
||||
}
|
||||
|
||||
impl WordArena {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
chunks: Vec::new(),
|
||||
offset: ARENA_CHUNK_WORDS, // forces first alloc to create a chunk
|
||||
free_list: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn alloc(&mut self, n_words: usize) -> *mut u64 {
|
||||
if n_words == 0 {
|
||||
return std::ptr::NonNull::<u64>::dangling().as_ptr();
|
||||
}
|
||||
// Check the free list before bump-allocating.
|
||||
if let Some((_, bucket)) = self.free_list.iter_mut().find(|(s, _)| *s == n_words)
|
||||
&& let Some(ptr) = bucket.pop()
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
if self.offset + n_words > ARENA_CHUNK_WORDS {
|
||||
let size = ARENA_CHUNK_WORDS.max(n_words);
|
||||
self.chunks.push(vec![0u64; size]);
|
||||
self.offset = 0;
|
||||
}
|
||||
let chunk = self.chunks.last_mut().unwrap();
|
||||
// SAFETY: Either a new chunk was just created with len = ARENA_CHUNK_WORDS.max(n_words),
|
||||
// in which case offset = 0 and n_words <= len; or an existing chunk is reused, in which
|
||||
// case offset + n_words <= ARENA_CHUNK_WORDS <= chunk.len().
|
||||
let ptr = unsafe { chunk.as_mut_ptr().add(self.offset) };
|
||||
self.offset += n_words;
|
||||
ptr
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn free(&mut self, ptr: *mut u64, n_words: usize, used_words: usize) {
|
||||
if n_words == 0 {
|
||||
return;
|
||||
}
|
||||
// Zero only the in-use words; the rest are already zero by the BitVec
|
||||
// invariant (data[words_in_use..capacity] is always zero).
|
||||
// SAFETY: ptr was returned by alloc(n_words) and is valid for n_words
|
||||
// words; used_words <= n_words.
|
||||
if used_words > 0 {
|
||||
unsafe { std::slice::from_raw_parts_mut(ptr, used_words).fill(0) };
|
||||
}
|
||||
if let Some((_, bucket)) = self.free_list.iter_mut().find(|(s, _)| *s == n_words) {
|
||||
bucket.push(ptr);
|
||||
} else {
|
||||
self.free_list.push((n_words, vec![ptr]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static WORD_ARENA: std::cell::RefCell<WordArena> = const { std::cell::RefCell::new(WordArena::new()) };
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn arena_alloc(n_words: usize) -> *mut u64 {
|
||||
WORD_ARENA.with(|a| a.borrow_mut().alloc(n_words))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn arena_free(ptr: *mut u64, n_words: usize, used_words: usize) {
|
||||
WORD_ARENA.with(|a| a.borrow_mut().free(ptr, n_words, used_words));
|
||||
}
|
||||
|
||||
/// A bit vector whose backing `u64` words are bump-allocated from a global
|
||||
/// arena. Token sets are OR'd together many times and doing this at the word
|
||||
/// level rather than bit-by-bit is much faster.
|
||||
pub struct BitVec {
|
||||
/// Pointer into arena chunk data. Dangling when `capacity == 0`.
|
||||
data: *mut u64,
|
||||
num_bits: u32,
|
||||
/// Number of allocated words (_not_ bytes) in the arena region.
|
||||
capacity: u32,
|
||||
}
|
||||
|
||||
impl BitVec {
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
data: ptr::NonNull::dangling().as_ptr(),
|
||||
num_bits: 0,
|
||||
capacity: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_capacity(n_bits: usize) -> Self {
|
||||
let n_words = n_bits.div_ceil(64);
|
||||
if n_words == 0 {
|
||||
return Self::new();
|
||||
}
|
||||
Self {
|
||||
data: arena_alloc(n_words),
|
||||
num_bits: 0,
|
||||
capacity: n_words as u32,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn words_in_use(&self) -> usize {
|
||||
(self.num_bits as usize).div_ceil(64)
|
||||
}
|
||||
|
||||
/// View the in-use words as a slice.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn as_slice(&self) -> &[u64] {
|
||||
let n = self.words_in_use();
|
||||
if n == 0 {
|
||||
&[]
|
||||
} else {
|
||||
// SAFETY: data points to at least `capacity` valid words, and
|
||||
// words_in_use() <= capacity.
|
||||
unsafe { std::slice::from_raw_parts(self.data, n) }
|
||||
}
|
||||
}
|
||||
|
||||
/// View all `capacity` allocated words as a mutable slice.
|
||||
#[inline]
|
||||
const fn as_full_slice_mut(&mut self) -> &mut [u64] {
|
||||
let n = self.capacity as usize;
|
||||
if n == 0 {
|
||||
return &mut [];
|
||||
}
|
||||
// SAFETY: data points to `capacity` valid words.
|
||||
unsafe { std::slice::from_raw_parts_mut(self.data, n) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub const fn len(&self) -> usize {
|
||||
self.num_bits as usize
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn get(&self, index: usize) -> Option<bool> {
|
||||
if index >= self.num_bits as usize {
|
||||
return None;
|
||||
}
|
||||
Some(self.as_slice()[index / 64] >> (index % 64) & 1 != 0)
|
||||
}
|
||||
|
||||
pub const fn set(&mut self, index: usize, val: bool) {
|
||||
let word_idx = index / 64;
|
||||
let bit_idx = index % 64;
|
||||
let words = self.as_full_slice_mut();
|
||||
if val {
|
||||
words[word_idx] |= 1u64 << bit_idx;
|
||||
} else {
|
||||
words[word_idx] &= !(1u64 << bit_idx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Grow the backing storage so that the arena region holds at least
|
||||
/// `n_words` words, copying existing data into the new region.
|
||||
fn ensure_words(&mut self, n_words: usize) {
|
||||
if n_words > self.capacity as usize {
|
||||
let new_cap = n_words.max((self.capacity as usize) * 2);
|
||||
let new_data = arena_alloc(new_cap);
|
||||
let old = self.words_in_use();
|
||||
if old > 0 {
|
||||
// SAFETY: new_data points to new_cap valid zeroed words; old <= capacity.
|
||||
let dst = unsafe { std::slice::from_raw_parts_mut(new_data, old) };
|
||||
dst.copy_from_slice(self.as_slice());
|
||||
}
|
||||
let old_cap = self.capacity;
|
||||
let old_data = self.data;
|
||||
self.data = new_data;
|
||||
self.capacity = new_cap as u32;
|
||||
arena_free(old_data, old_cap as usize, old);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, new_len: usize, val: bool) {
|
||||
let new_words = new_len.div_ceil(64);
|
||||
let old_words = self.words_in_use();
|
||||
self.ensure_words(new_words);
|
||||
let fill = if val { !0u64 } else { 0u64 };
|
||||
let words = self.as_full_slice_mut();
|
||||
if new_words > old_words {
|
||||
words[old_words..new_words].fill(fill);
|
||||
// When setting bits to true, clear any bits in the last word that
|
||||
// fall beyond new_len to preserve the invariant that bits >= num_bits
|
||||
// are always zero.
|
||||
if val && !new_len.is_multiple_of(64) {
|
||||
let mask = (1u64 << (new_len % 64)) - 1;
|
||||
words[new_words - 1] &= mask;
|
||||
}
|
||||
} else if new_words < old_words {
|
||||
// Zero out truncated words so stale data is never visible.
|
||||
words[new_words..old_words].fill(0);
|
||||
}
|
||||
self.num_bits = new_len as u32;
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn last(&self) -> Option<bool> {
|
||||
if self.num_bits == 0 {
|
||||
return None;
|
||||
}
|
||||
self.get(self.num_bits as usize - 1)
|
||||
}
|
||||
|
||||
pub const fn pop(&mut self) -> Option<bool> {
|
||||
if self.num_bits == 0 {
|
||||
return None;
|
||||
}
|
||||
self.num_bits -= 1;
|
||||
let word_idx = self.num_bits as usize / 64;
|
||||
let bit_idx = self.num_bits as usize % 64;
|
||||
let new_words_in_use = self.words_in_use();
|
||||
let words = self.as_full_slice_mut();
|
||||
let val = words[word_idx] >> bit_idx & 1 != 0;
|
||||
if val {
|
||||
words[word_idx] &= !(1u64 << bit_idx);
|
||||
}
|
||||
// Zero out the word if it's no longer in use.
|
||||
if word_idx >= new_words_in_use {
|
||||
words[word_idx] = 0;
|
||||
}
|
||||
Some(val)
|
||||
}
|
||||
|
||||
/// Word-level OR: self |= other. Returns true if any new bits were set.
|
||||
#[inline]
|
||||
pub fn insert_all(&mut self, other: &Self) -> bool {
|
||||
let other_words = other.words_in_use();
|
||||
if other_words == 0 {
|
||||
return false;
|
||||
}
|
||||
let self_words = self.words_in_use();
|
||||
if other_words > self.capacity as usize {
|
||||
// Need a larger arena region.
|
||||
let new_data = arena_alloc(other_words);
|
||||
if self_words > 0 {
|
||||
// SAFETY: new_data points to other_words valid zeroed words; self_words <= capacity.
|
||||
let dst = unsafe { std::slice::from_raw_parts_mut(new_data, self_words) };
|
||||
dst.copy_from_slice(self.as_slice());
|
||||
}
|
||||
// Arena memory is pre-zeroed, so words self_words..other_words are already 0.
|
||||
let old_cap = self.capacity;
|
||||
let old_data = self.data;
|
||||
self.data = new_data;
|
||||
self.capacity = other_words as u32;
|
||||
arena_free(old_data, old_cap as usize, self_words);
|
||||
} else if other_words > self_words {
|
||||
// Have capacity, but clear any stale data in the region we're about to OR into.
|
||||
self.as_full_slice_mut()[self_words..other_words].fill(0);
|
||||
}
|
||||
if other.num_bits > self.num_bits {
|
||||
self.num_bits = other.num_bits;
|
||||
}
|
||||
let other_slice = other.as_slice();
|
||||
let self_slice = &mut self.as_full_slice_mut()[..other_words];
|
||||
let mut any_new = 0u64;
|
||||
for (sw, &ow) in self_slice.iter_mut().zip(other_slice) {
|
||||
let new_bits = ow & !*sw;
|
||||
*sw |= ow;
|
||||
any_new |= new_bits;
|
||||
}
|
||||
any_new != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BitVec {
|
||||
fn drop(&mut self) {
|
||||
if self.capacity > 0 {
|
||||
arena_free(self.data, self.capacity as usize, self.words_in_use());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BitVec {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for BitVec {
|
||||
fn clone(&self) -> Self {
|
||||
let words = self.words_in_use();
|
||||
if words == 0 {
|
||||
return Self::new();
|
||||
}
|
||||
let new_data = arena_alloc(words);
|
||||
// SAFETY: new_data points to `words` valid zeroed words.
|
||||
let dst = unsafe { std::slice::from_raw_parts_mut(new_data, words) };
|
||||
dst.copy_from_slice(self.as_slice());
|
||||
Self {
|
||||
data: new_data,
|
||||
num_bits: self.num_bits,
|
||||
capacity: words as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for BitVec {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
let a = self.as_slice();
|
||||
let b = other.as_slice();
|
||||
let max_len = a.len().max(b.len());
|
||||
for i in 0..max_len {
|
||||
if a.get(i).copied().unwrap_or(0) != b.get(i).copied().unwrap_or(0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for BitVec {}
|
||||
|
||||
impl Hash for BitVec {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
let data = self.as_slice();
|
||||
let effective_len = data.iter().rposition(|&w| w != 0).map_or(0, |i| i + 1);
|
||||
data[..effective_len].hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for BitVec {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
let a = self.as_slice();
|
||||
let b = other.as_slice();
|
||||
let max_len = a.len().max(b.len());
|
||||
for i in 0..max_len {
|
||||
let aw = a.get(i).copied().unwrap_or(0);
|
||||
let bw = b.get(i).copied().unwrap_or(0);
|
||||
if aw != bw {
|
||||
let first_diff = (aw ^ bw).trailing_zeros();
|
||||
return if (aw >> first_diff) & 1 != 0 {
|
||||
std::cmp::Ordering::Greater
|
||||
} else {
|
||||
std::cmp::Ordering::Less
|
||||
};
|
||||
}
|
||||
}
|
||||
std::cmp::Ordering::Equal
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for BitVec {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<usize> for BitVec {
|
||||
type Output = bool;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
static TRUE: bool = true;
|
||||
static FALSE: bool = false;
|
||||
if self.as_slice()[index / 64] >> (index % 64) & 1 != 0 {
|
||||
&TRUE
|
||||
} else {
|
||||
&FALSE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator that yields only the indices of set bits, skipping zero words
|
||||
/// entirely and using `trailing_zeros()` within each word.
|
||||
pub struct SetBitsIter<'a> {
|
||||
data: &'a [u64],
|
||||
word_idx: usize,
|
||||
current_word: u64,
|
||||
}
|
||||
|
||||
impl<'a> SetBitsIter<'a> {
|
||||
#[must_use]
|
||||
pub fn new(data: &'a [u64]) -> Self {
|
||||
Self {
|
||||
data,
|
||||
word_idx: 0,
|
||||
current_word: data.first().copied().unwrap_or(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SetBitsIter<'_> {
|
||||
type Item = usize;
|
||||
|
||||
fn next(&mut self) -> Option<usize> {
|
||||
while self.current_word == 0 {
|
||||
self.word_idx += 1;
|
||||
if self.word_idx >= self.data.len() {
|
||||
return None;
|
||||
}
|
||||
self.current_word = self.data[self.word_idx];
|
||||
}
|
||||
let bit = self.current_word.trailing_zeros() as usize;
|
||||
self.current_word &= self.current_word - 1; // clear lowest set bit
|
||||
Some(self.word_idx * 64 + bit)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,31 +6,28 @@ mod item_set_builder;
|
|||
mod minimize_parse_table;
|
||||
mod token_conflicts;
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
|
||||
pub use build_lex_table::LARGE_CHARACTER_RANGE_COUNT;
|
||||
use build_parse_table::BuildTableResult;
|
||||
pub use build_parse_table::ParseTableBuilderError;
|
||||
use log::{debug, info};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use self::{
|
||||
build_lex_table::build_lex_table,
|
||||
build_parse_table::{ParseStateInfo, build_parse_table},
|
||||
build_parse_table::{build_parse_table, ParseStateInfo},
|
||||
coincident_tokens::CoincidentTokenIndex,
|
||||
item::ItemKeyMap,
|
||||
item_set_builder::ParseItemSetBuilder,
|
||||
minimize_parse_table::minimize_parse_table,
|
||||
token_conflicts::TokenConflictMap,
|
||||
};
|
||||
use crate::{
|
||||
Diagnostic, OptLevel,
|
||||
grammars::{InlinedProductionMap, LexicalGrammar, SyntaxGrammar},
|
||||
nfa::{CharacterSet, NfaCursor},
|
||||
node_types::VariableInfo,
|
||||
rules::{AliasMap, Symbol, SymbolType, TokenSet},
|
||||
strpool::StrPool,
|
||||
tables::{ActionList, ActionListPool, LexTable, ParseAction, ParseTable, ParseTableEntry},
|
||||
tables::{LexTable, ParseAction, ParseTable, ParseTableEntry},
|
||||
OptLevel,
|
||||
};
|
||||
|
||||
pub struct Tables {
|
||||
|
|
@ -40,42 +37,32 @@ pub struct Tables {
|
|||
pub large_character_sets: Vec<(Option<Symbol>, CharacterSet)>,
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::too_many_arguments,
|
||||
reason = "all parameters are required for table building"
|
||||
)]
|
||||
pub fn build_tables(
|
||||
syntax_grammar: &SyntaxGrammar,
|
||||
lexical_grammar: &LexicalGrammar,
|
||||
simple_aliases: &AliasMap,
|
||||
variable_info: &[VariableInfo],
|
||||
inlines: &InlinedProductionMap,
|
||||
str_pool: &StrPool,
|
||||
report_symbol_name: Option<&str>,
|
||||
optimizations: OptLevel,
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
) -> BuildTableResult<Tables> {
|
||||
let item_key_map = ItemKeyMap::new(syntax_grammar, str_pool);
|
||||
let item_set_builder =
|
||||
ParseItemSetBuilder::new(syntax_grammar, lexical_grammar, inlines, &item_key_map);
|
||||
let following_tokens = get_following_tokens(syntax_grammar, lexical_grammar, &item_set_builder);
|
||||
let item_set_builder = ParseItemSetBuilder::new(syntax_grammar, lexical_grammar, inlines);
|
||||
let following_tokens =
|
||||
get_following_tokens(syntax_grammar, lexical_grammar, inlines, &item_set_builder);
|
||||
let (mut parse_table, parse_state_info) = build_parse_table(
|
||||
syntax_grammar,
|
||||
lexical_grammar,
|
||||
item_set_builder,
|
||||
variable_info,
|
||||
str_pool,
|
||||
diagnostics,
|
||||
)?;
|
||||
let token_conflict_map = TokenConflictMap::new(lexical_grammar, following_tokens);
|
||||
let coincident_token_index =
|
||||
CoincidentTokenIndex::new(&parse_table, lexical_grammar, syntax_grammar.word_token);
|
||||
let coincident_token_index = CoincidentTokenIndex::new(&parse_table, lexical_grammar);
|
||||
let keywords = identify_keywords(
|
||||
lexical_grammar,
|
||||
&parse_table,
|
||||
syntax_grammar.word_token,
|
||||
&token_conflict_map,
|
||||
&coincident_token_index,
|
||||
str_pool,
|
||||
);
|
||||
populate_error_state(
|
||||
&mut parse_table,
|
||||
|
|
@ -84,10 +71,8 @@ pub fn build_tables(
|
|||
&coincident_token_index,
|
||||
&token_conflict_map,
|
||||
&keywords,
|
||||
str_pool,
|
||||
);
|
||||
populate_used_symbols(&mut parse_table, syntax_grammar, lexical_grammar);
|
||||
let mut parse_table = ActionListPool::intern_table(parse_table);
|
||||
minimize_parse_table(
|
||||
&mut parse_table,
|
||||
syntax_grammar,
|
||||
|
|
@ -95,7 +80,6 @@ pub fn build_tables(
|
|||
simple_aliases,
|
||||
&token_conflict_map,
|
||||
&keywords,
|
||||
str_pool,
|
||||
optimizations,
|
||||
);
|
||||
let lex_tables = build_lex_table(
|
||||
|
|
@ -107,10 +91,7 @@ pub fn build_tables(
|
|||
&token_conflict_map,
|
||||
);
|
||||
populate_external_lex_states(&mut parse_table, syntax_grammar);
|
||||
mark_fragile_tokens(&mut parse_table, &token_conflict_map);
|
||||
parse_table
|
||||
.action_lists
|
||||
.canonicalize(&mut parse_table.states);
|
||||
mark_fragile_tokens(&mut parse_table, lexical_grammar, &token_conflict_map);
|
||||
|
||||
if let Some(report_symbol_name) = report_symbol_name {
|
||||
report_state_info(
|
||||
|
|
@ -118,7 +99,6 @@ pub fn build_tables(
|
|||
lexical_grammar,
|
||||
&parse_table,
|
||||
&parse_state_info,
|
||||
str_pool,
|
||||
report_symbol_name,
|
||||
);
|
||||
}
|
||||
|
|
@ -138,25 +118,28 @@ pub fn build_tables(
|
|||
fn get_following_tokens(
|
||||
syntax_grammar: &SyntaxGrammar,
|
||||
lexical_grammar: &LexicalGrammar,
|
||||
inlines: &InlinedProductionMap,
|
||||
builder: &ParseItemSetBuilder,
|
||||
) -> Vec<TokenSet> {
|
||||
let n_terminals = lexical_grammar.variables.len();
|
||||
let n_externals = syntax_grammar.external_tokens.len();
|
||||
let mut result = vec![TokenSet::with_capacity(n_terminals, n_externals); n_terminals];
|
||||
let mut result = vec![TokenSet::new(); lexical_grammar.variables.len()];
|
||||
let productions = syntax_grammar
|
||||
.variables
|
||||
.iter()
|
||||
.flat_map(|v| &v.productions)
|
||||
.chain(&inlines.productions);
|
||||
let all_tokens = (0..result.len())
|
||||
.map(Symbol::terminal)
|
||||
.collect::<TokenSet>();
|
||||
for production in &syntax_grammar.productions {
|
||||
let steps = &syntax_grammar.steps[production.step_range()];
|
||||
for i in 1..steps.len() {
|
||||
let left_tokens = builder.last_set(steps[i - 1].symbol());
|
||||
let right_tokens = builder.first_set(steps[i].symbol());
|
||||
let right_reserved_tokens = builder.reserved_first_set(steps[i].symbol());
|
||||
for production in productions {
|
||||
for i in 1..production.steps.len() {
|
||||
let left_tokens = builder.last_set(&production.steps[i - 1].symbol);
|
||||
let right_tokens = builder.first_set(&production.steps[i].symbol);
|
||||
let right_reserved_tokens = builder.reserved_first_set(&production.steps[i].symbol);
|
||||
for left_token in left_tokens.iter() {
|
||||
if left_token.is_terminal() {
|
||||
result[left_token.index as usize].insert_all_terminals(right_tokens);
|
||||
result[left_token.index].insert_all_terminals(right_tokens);
|
||||
if let Some(reserved_tokens) = right_reserved_tokens {
|
||||
result[left_token.index as usize].insert_all_terminals(reserved_tokens);
|
||||
result[left_token.index].insert_all_terminals(reserved_tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -167,20 +150,19 @@ fn get_following_tokens(
|
|||
for entry in &mut result {
|
||||
entry.insert(*extra);
|
||||
}
|
||||
result[extra.index as usize] = all_tokens.clone();
|
||||
result[extra.index] = all_tokens.clone();
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn populate_error_state(
|
||||
parse_table: &mut ParseTable<ParseTableEntry>,
|
||||
parse_table: &mut ParseTable,
|
||||
syntax_grammar: &SyntaxGrammar,
|
||||
lexical_grammar: &LexicalGrammar,
|
||||
coincident_token_index: &CoincidentTokenIndex,
|
||||
token_conflict_map: &TokenConflictMap,
|
||||
keywords: &TokenSet,
|
||||
str_pool: &StrPool,
|
||||
) {
|
||||
let state = &mut parse_table.states[0];
|
||||
let n = lexical_grammar.variables.len();
|
||||
|
|
@ -199,7 +181,7 @@ fn populate_error_state(
|
|||
} else {
|
||||
debug!(
|
||||
"error recovery - token {} has no conflicts",
|
||||
str_pool.resolve(lexical_grammar.variables[i].name)
|
||||
lexical_grammar.variables[i].name
|
||||
);
|
||||
Some(Symbol::terminal(i))
|
||||
}
|
||||
|
|
@ -208,31 +190,31 @@ fn populate_error_state(
|
|||
|
||||
let recover_entry = ParseTableEntry {
|
||||
reusable: false,
|
||||
actions: ActionList::One(ParseAction::Recover),
|
||||
actions: vec![ParseAction::Recover],
|
||||
};
|
||||
|
||||
// Exclude from the error-recovery state any token that conflicts with one of
|
||||
// the *conflict-free tokens* identified above.
|
||||
for i in 0..n {
|
||||
let symbol = Symbol::terminal(i);
|
||||
if !conflict_free_tokens.contains(symbol)
|
||||
&& !keywords.contains(symbol)
|
||||
if !conflict_free_tokens.contains(&symbol)
|
||||
&& !keywords.contains(&symbol)
|
||||
&& syntax_grammar.word_token != Some(symbol)
|
||||
&& let Some(t) = conflict_free_tokens.iter().find(|t| {
|
||||
!coincident_token_index.contains(symbol, *t)
|
||||
&& token_conflict_map.does_conflict(symbol.index as usize, t.index as usize)
|
||||
})
|
||||
{
|
||||
debug!(
|
||||
"error recovery - exclude token {} because of conflict with {}",
|
||||
str_pool.resolve(lexical_grammar.variables[i].name),
|
||||
str_pool.resolve(lexical_grammar.variables[t.index as usize].name)
|
||||
);
|
||||
continue;
|
||||
if let Some(t) = conflict_free_tokens.iter().find(|t| {
|
||||
!coincident_token_index.contains(symbol, *t)
|
||||
&& token_conflict_map.does_conflict(symbol.index, t.index)
|
||||
}) {
|
||||
debug!(
|
||||
"error recovery - exclude token {} because of conflict with {}",
|
||||
lexical_grammar.variables[i].name, lexical_grammar.variables[t.index].name
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
debug!(
|
||||
"error recovery - include token {}",
|
||||
str_pool.resolve(lexical_grammar.variables[i].name)
|
||||
lexical_grammar.variables[i].name
|
||||
);
|
||||
state
|
||||
.terminal_entries
|
||||
|
|
@ -253,7 +235,7 @@ fn populate_error_state(
|
|||
}
|
||||
|
||||
fn populate_used_symbols(
|
||||
parse_table: &mut ParseTable<ParseTableEntry>,
|
||||
parse_table: &mut ParseTable,
|
||||
syntax_grammar: &SyntaxGrammar,
|
||||
lexical_grammar: &LexicalGrammar,
|
||||
) {
|
||||
|
|
@ -263,13 +245,13 @@ fn populate_used_symbols(
|
|||
for state in &parse_table.states {
|
||||
for symbol in state.terminal_entries.keys() {
|
||||
match symbol.kind {
|
||||
SymbolType::Terminal => terminal_usages[symbol.index as usize] = true,
|
||||
SymbolType::External => external_usages[symbol.index as usize] = true,
|
||||
SymbolType::Terminal => terminal_usages[symbol.index] = true,
|
||||
SymbolType::External => external_usages[symbol.index] = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
for symbol in state.nonterminal_entries.keys() {
|
||||
non_terminal_usages[symbol.index as usize] = true;
|
||||
non_terminal_usages[symbol.index] = true;
|
||||
}
|
||||
}
|
||||
parse_table.symbols.push(Symbol::end());
|
||||
|
|
@ -281,10 +263,7 @@ fn populate_used_symbols(
|
|||
// ensure that a subtree's symbol can be successfully reassigned to the word token
|
||||
// without having to move the subtree to the heap.
|
||||
// See https://github.com/tree-sitter/tree-sitter/issues/258
|
||||
if syntax_grammar
|
||||
.word_token
|
||||
.is_some_and(|t| t.index as usize == i)
|
||||
{
|
||||
if syntax_grammar.word_token.is_some_and(|t| t.index == i) {
|
||||
parse_table.symbols.insert(1, Symbol::terminal(i));
|
||||
} else {
|
||||
parse_table.symbols.push(Symbol::terminal(i));
|
||||
|
|
@ -304,7 +283,7 @@ fn populate_used_symbols(
|
|||
}
|
||||
|
||||
fn populate_external_lex_states(parse_table: &mut ParseTable, syntax_grammar: &SyntaxGrammar) {
|
||||
let mut external_tokens_by_corresponding_internal_token = FxHashMap::default();
|
||||
let mut external_tokens_by_corresponding_internal_token = HashMap::new();
|
||||
for (i, external_token) in syntax_grammar.external_tokens.iter().enumerate() {
|
||||
if let Some(symbol) = external_token.corresponding_internal_token {
|
||||
external_tokens_by_corresponding_internal_token.insert(symbol.index, i);
|
||||
|
|
@ -320,11 +299,12 @@ fn populate_external_lex_states(parse_table: &mut ParseTable, syntax_grammar: &S
|
|||
for token in parse_table.states[i].terminal_entries.keys() {
|
||||
if token.is_external() {
|
||||
external_tokens.insert(*token);
|
||||
} else if token.is_terminal()
|
||||
&& let Some(index) =
|
||||
} else if token.is_terminal() {
|
||||
if let Some(index) =
|
||||
external_tokens_by_corresponding_internal_token.get(&token.index)
|
||||
{
|
||||
external_tokens.insert(Symbol::external(*index));
|
||||
{
|
||||
external_tokens.insert(Symbol::external(*index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -335,16 +315,16 @@ fn populate_external_lex_states(parse_table: &mut ParseTable, syntax_grammar: &S
|
|||
.unwrap_or_else(|| {
|
||||
parse_table.external_lex_states.push(external_tokens);
|
||||
parse_table.external_lex_states.len() - 1
|
||||
}) as u32;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn identify_keywords(
|
||||
lexical_grammar: &LexicalGrammar,
|
||||
parse_table: &ParseTable,
|
||||
word_token: Option<Symbol>,
|
||||
token_conflict_map: &TokenConflictMap,
|
||||
coincident_token_index: &CoincidentTokenIndex,
|
||||
str_pool: &StrPool,
|
||||
) -> TokenSet {
|
||||
if word_token.is_none() {
|
||||
return TokenSet::new();
|
||||
|
|
@ -362,12 +342,12 @@ fn identify_keywords(
|
|||
.filter_map(|(i, variable)| {
|
||||
cursor.reset(vec![variable.start_state]);
|
||||
if all_chars_are_alphabetical(&cursor)
|
||||
&& token_conflict_map.does_match_same_string(i, word_token.index as usize)
|
||||
&& !token_conflict_map.does_match_different_string(i, word_token.index as usize)
|
||||
&& token_conflict_map.does_match_same_string(i, word_token.index)
|
||||
&& !token_conflict_map.does_match_different_string(i, word_token.index)
|
||||
{
|
||||
debug!(
|
||||
"Keywords - add candidate {}",
|
||||
str_pool.resolve(lexical_grammar.variables[i].name)
|
||||
lexical_grammar.variables[i].name
|
||||
);
|
||||
Some(Symbol::terminal(i))
|
||||
} else {
|
||||
|
|
@ -382,14 +362,12 @@ fn identify_keywords(
|
|||
.filter(|token| {
|
||||
for other_token in keyword_candidates.iter() {
|
||||
if other_token != *token
|
||||
&& token_conflict_map
|
||||
.does_match_same_string(other_token.index as usize, token.index as usize)
|
||||
&& token_conflict_map.does_match_same_string(other_token.index, token.index)
|
||||
{
|
||||
debug!(
|
||||
"Keywords - exclude {} because it matches the same string as {}",
|
||||
str_pool.resolve(lexical_grammar.variables[token.index as usize].name),
|
||||
str_pool
|
||||
.resolve(lexical_grammar.variables[other_token.index as usize].name)
|
||||
lexical_grammar.variables[token.index].name,
|
||||
lexical_grammar.variables[other_token.index].name
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -400,12 +378,11 @@ fn identify_keywords(
|
|||
|
||||
// Exclude keyword candidates for which substituting the keyword capture
|
||||
// token would introduce new lexical conflicts with other tokens.
|
||||
|
||||
keywords
|
||||
let keywords = keywords
|
||||
.iter()
|
||||
.filter(|token| {
|
||||
for other_index in 0..lexical_grammar.variables.len() {
|
||||
if keyword_candidates.contains(Symbol::terminal(other_index)) {
|
||||
if keyword_candidates.contains(&Symbol::terminal(other_index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -413,20 +390,26 @@ fn identify_keywords(
|
|||
// this keyword candidate, then substituting the word token won't
|
||||
// introduce any new lexical conflicts.
|
||||
if coincident_token_index
|
||||
.all_coincident_states_have_word(*token, Symbol::terminal(other_index))
|
||||
.states_with(*token, Symbol::terminal(other_index))
|
||||
.iter()
|
||||
.all(|state_id| {
|
||||
parse_table.states[*state_id]
|
||||
.terminal_entries
|
||||
.contains_key(&word_token)
|
||||
})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if !token_conflict_map.has_same_conflict_status(
|
||||
token.index as usize,
|
||||
word_token.index as usize,
|
||||
token.index,
|
||||
word_token.index,
|
||||
other_index,
|
||||
) {
|
||||
debug!(
|
||||
"Keywords - exclude {} because of conflict with {}",
|
||||
str_pool.resolve(lexical_grammar.variables[token.index as usize].name),
|
||||
str_pool.resolve(lexical_grammar.variables[other_index].name)
|
||||
lexical_grammar.variables[token.index].name,
|
||||
lexical_grammar.variables[other_index].name
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -434,27 +417,35 @@ fn identify_keywords(
|
|||
|
||||
debug!(
|
||||
"Keywords - include {}",
|
||||
str_pool.resolve(lexical_grammar.variables[token.index as usize].name),
|
||||
lexical_grammar.variables[token.index].name,
|
||||
);
|
||||
true
|
||||
})
|
||||
.collect()
|
||||
.collect();
|
||||
|
||||
keywords
|
||||
}
|
||||
|
||||
fn mark_fragile_tokens(parse_table: &mut ParseTable, token_conflict_map: &TokenConflictMap) {
|
||||
let mut valid_terminal_indices = Vec::new();
|
||||
fn mark_fragile_tokens(
|
||||
parse_table: &mut ParseTable,
|
||||
lexical_grammar: &LexicalGrammar,
|
||||
token_conflict_map: &TokenConflictMap,
|
||||
) {
|
||||
let n = lexical_grammar.variables.len();
|
||||
let mut valid_tokens_mask = Vec::with_capacity(n);
|
||||
for state in &mut parse_table.states {
|
||||
valid_terminal_indices.clear();
|
||||
valid_tokens_mask.clear();
|
||||
valid_tokens_mask.resize(n, false);
|
||||
for token in state.terminal_entries.keys() {
|
||||
if token.is_terminal() {
|
||||
valid_terminal_indices.push(token.index);
|
||||
valid_tokens_mask[token.index] = true;
|
||||
}
|
||||
}
|
||||
for (token, id) in &mut state.terminal_entries {
|
||||
for (token, entry) in &mut state.terminal_entries {
|
||||
if token.is_terminal() {
|
||||
for &i in &valid_terminal_indices {
|
||||
if token_conflict_map.does_overlap(i as usize, token.index as usize) {
|
||||
id.set_reusable(false);
|
||||
for (i, is_valid) in valid_tokens_mask.iter().enumerate() {
|
||||
if *is_valid && token_conflict_map.does_overlap(i, token.index) {
|
||||
entry.reusable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -467,8 +458,7 @@ fn report_state_info<'a>(
|
|||
syntax_grammar: &SyntaxGrammar,
|
||||
lexical_grammar: &LexicalGrammar,
|
||||
parse_table: &ParseTable,
|
||||
parse_state_info: &ParseStateInfo<'a>,
|
||||
str_pool: &StrPool,
|
||||
parse_state_info: &[ParseStateInfo<'a>],
|
||||
report_symbol_name: &'a str,
|
||||
) {
|
||||
let mut all_state_indices = BTreeSet::new();
|
||||
|
|
@ -478,8 +468,8 @@ fn report_state_info<'a>(
|
|||
|
||||
for (i, state) in parse_table.states.iter().enumerate() {
|
||||
all_state_indices.insert(i);
|
||||
let item_set = parse_state_info.item_set(state.id);
|
||||
for entry in &item_set.entries {
|
||||
let item_set = &parse_state_info[state.id];
|
||||
for entry in &item_set.1.entries {
|
||||
if !entry.item.is_augmented() {
|
||||
symbols_with_state_indices[entry.item.variable_index as usize]
|
||||
.1
|
||||
|
|
@ -493,13 +483,13 @@ fn report_state_info<'a>(
|
|||
let max_symbol_name_length = syntax_grammar
|
||||
.variables
|
||||
.iter()
|
||||
.map(|v| str_pool.resolve(v.name).len())
|
||||
.map(|v| v.name.len())
|
||||
.max()
|
||||
.unwrap();
|
||||
for (symbol, states) in &symbols_with_state_indices {
|
||||
info!(
|
||||
"{:width$}\t{}",
|
||||
str_pool.resolve(syntax_grammar.variables[symbol.index as usize].name),
|
||||
syntax_grammar.variables[symbol.index].name,
|
||||
states.len(),
|
||||
width = max_symbol_name_length
|
||||
);
|
||||
|
|
@ -512,9 +502,7 @@ fn report_state_info<'a>(
|
|||
symbols_with_state_indices
|
||||
.iter()
|
||||
.find_map(|(symbol, state_indices)| {
|
||||
if str_pool.resolve(syntax_grammar.variables[symbol.index as usize].name)
|
||||
== report_symbol_name
|
||||
{
|
||||
if syntax_grammar.variables[symbol.index].name == report_symbol_name {
|
||||
Some(state_indices)
|
||||
} else {
|
||||
None
|
||||
|
|
@ -528,8 +516,7 @@ fn report_state_info<'a>(
|
|||
|
||||
for state_index in state_indices {
|
||||
let id = parse_table.states[state_index].id;
|
||||
let preceding_symbols = &parse_state_info.preceding_symbols_by_id[id as usize];
|
||||
let item_set = parse_state_info.item_set(id);
|
||||
let (preceding_symbols, item_set) = &parse_state_info[id];
|
||||
info!("state index: {state_index}");
|
||||
info!("state id: {id}");
|
||||
info!(
|
||||
|
|
@ -538,12 +525,11 @@ fn report_state_info<'a>(
|
|||
.iter()
|
||||
.map(|symbol| {
|
||||
if symbol.is_terminal() {
|
||||
str_pool.resolve(lexical_grammar.variables[symbol.index as usize].name)
|
||||
lexical_grammar.variables[symbol.index].name.clone()
|
||||
} else if symbol.is_external() {
|
||||
str_pool
|
||||
.resolve(syntax_grammar.external_tokens[symbol.index as usize].name)
|
||||
syntax_grammar.external_tokens[symbol.index].name.clone()
|
||||
} else {
|
||||
str_pool.resolve(syntax_grammar.variables[symbol.index as usize].name)
|
||||
syntax_grammar.variables[symbol.index].name.clone()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
|
|
@ -551,13 +537,7 @@ fn report_state_info<'a>(
|
|||
);
|
||||
info!(
|
||||
"\nitems:\n{}",
|
||||
item::ParseItemSetDisplay(
|
||||
item_set,
|
||||
syntax_grammar,
|
||||
lexical_grammar,
|
||||
str_pool,
|
||||
&parse_state_info.lookaheads
|
||||
),
|
||||
item::ParseItemSetDisplay(item_set, syntax_grammar, lexical_grammar),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
use std::{
|
||||
collections::{VecDeque, hash_map::Entry},
|
||||
collections::{hash_map::Entry, HashMap, VecDeque},
|
||||
mem,
|
||||
};
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use log::debug;
|
||||
|
||||
use super::{coincident_tokens::CoincidentTokenIndex, token_conflicts::TokenConflictMap};
|
||||
|
|
@ -13,7 +11,7 @@ use crate::{
|
|||
grammars::{LexicalGrammar, SyntaxGrammar},
|
||||
nfa::{CharacterSet, NfaCursor},
|
||||
rules::{Symbol, TokenSet},
|
||||
tables::{AdvanceAction, LexState, LexStateId, LexTable, ParseStateId, ParseTable},
|
||||
tables::{AdvanceAction, LexState, LexTable, ParseStateId, ParseTable},
|
||||
};
|
||||
|
||||
pub const LARGE_CHARACTER_RANGE_COUNT: usize = 8;
|
||||
|
|
@ -49,7 +47,7 @@ pub fn build_lex_table(
|
|||
.chain(state.reserved_words.iter())
|
||||
.filter_map(|token| {
|
||||
if token.is_terminal() {
|
||||
if keywords.contains(token) {
|
||||
if keywords.contains(&token) {
|
||||
syntax_grammar.word_token
|
||||
} else {
|
||||
Some(token)
|
||||
|
|
@ -67,17 +65,18 @@ pub fn build_lex_table(
|
|||
if merge_token_set(
|
||||
&mut entry.0,
|
||||
&tokens,
|
||||
lexical_grammar,
|
||||
token_conflict_map,
|
||||
coincident_token_index,
|
||||
) {
|
||||
did_merge = true;
|
||||
entry.1.push(i as u32);
|
||||
entry.1.push(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !did_merge {
|
||||
parse_state_ids_by_token_set.push((tokens, vec![i as u32]));
|
||||
parse_state_ids_by_token_set.push((tokens, vec![i]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +84,7 @@ pub fn build_lex_table(
|
|||
for (tokens, parse_state_ids) in parse_state_ids_by_token_set {
|
||||
let lex_state_id = builder.add_state_for_tokens(&tokens);
|
||||
for id in parse_state_ids {
|
||||
parse_table.states[id as usize].lex_state_id = lex_state_id;
|
||||
parse_table.states[id].lex_state_id = lex_state_id;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +130,7 @@ pub fn build_lex_table(
|
|||
}
|
||||
|
||||
struct QueueEntry {
|
||||
state_id: LexStateId,
|
||||
state_id: usize,
|
||||
nfa_states: Vec<u32>,
|
||||
eof_valid: bool,
|
||||
}
|
||||
|
|
@ -141,7 +140,7 @@ struct LexTableBuilder<'a> {
|
|||
cursor: NfaCursor<'a>,
|
||||
table: LexTable,
|
||||
state_queue: VecDeque<QueueEntry>,
|
||||
state_ids_by_nfa_state_set: FxHashMap<(Vec<u32>, bool), LexStateId>,
|
||||
state_ids_by_nfa_state_set: HashMap<(Vec<u32>, bool), usize>,
|
||||
}
|
||||
|
||||
impl<'a> LexTableBuilder<'a> {
|
||||
|
|
@ -151,7 +150,7 @@ impl<'a> LexTableBuilder<'a> {
|
|||
cursor: NfaCursor::new(&lexical_grammar.nfa, vec![]),
|
||||
table: LexTable::default(),
|
||||
state_queue: VecDeque::new(),
|
||||
state_ids_by_nfa_state_set: FxHashMap::default(),
|
||||
state_ids_by_nfa_state_set: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,13 +160,13 @@ impl<'a> LexTableBuilder<'a> {
|
|||
self.state_ids_by_nfa_state_set.clear();
|
||||
}
|
||||
|
||||
fn add_state_for_tokens(&mut self, tokens: &TokenSet) -> LexStateId {
|
||||
fn add_state_for_tokens(&mut self, tokens: &TokenSet) -> usize {
|
||||
let mut eof_valid = false;
|
||||
let nfa_states = tokens
|
||||
.iter()
|
||||
.filter_map(|token| {
|
||||
if token.is_terminal() {
|
||||
Some(self.lexical_grammar.variables[token.index as usize].start_state)
|
||||
Some(self.lexical_grammar.variables[token.index].start_state)
|
||||
} else {
|
||||
eof_valid = true;
|
||||
None
|
||||
|
|
@ -181,7 +180,7 @@ impl<'a> LexTableBuilder<'a> {
|
|||
"entry point state: {state_id}, tokens: {:?}",
|
||||
tokens
|
||||
.iter()
|
||||
.map(|t| &self.lexical_grammar.variables[t.index as usize].name)
|
||||
.map(|t| &self.lexical_grammar.variables[t.index].name)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
|
@ -197,7 +196,7 @@ impl<'a> LexTableBuilder<'a> {
|
|||
state_id
|
||||
}
|
||||
|
||||
fn add_state(&mut self, nfa_states: Vec<u32>, eof_valid: bool) -> (LexStateId, bool) {
|
||||
fn add_state(&mut self, nfa_states: Vec<u32>, eof_valid: bool) -> (usize, bool) {
|
||||
self.cursor.reset(nfa_states);
|
||||
match self
|
||||
.state_ids_by_nfa_state_set
|
||||
|
|
@ -205,7 +204,7 @@ impl<'a> LexTableBuilder<'a> {
|
|||
{
|
||||
Entry::Occupied(o) => (*o.get(), false),
|
||||
Entry::Vacant(v) => {
|
||||
let state_id = self.table.states.len() as u32;
|
||||
let state_id = self.table.states.len();
|
||||
self.table.states.push(LexState::default());
|
||||
self.state_queue.push_back(QueueEntry {
|
||||
state_id,
|
||||
|
|
@ -218,52 +217,53 @@ impl<'a> LexTableBuilder<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn populate_state(&mut self, state_id: LexStateId, nfa_states: Vec<u32>, eof_valid: bool) {
|
||||
fn populate_state(&mut self, state_id: usize, nfa_states: Vec<u32>, eof_valid: bool) {
|
||||
self.cursor.force_reset(nfa_states);
|
||||
|
||||
// The EOF state is represented as an empty list of NFA states.
|
||||
let mut completion = None;
|
||||
for (id, prec) in self.cursor.completions() {
|
||||
if let Some((prev_id, prev_precedence)) = completion
|
||||
&& TokenConflictMap::prefer_token(
|
||||
if let Some((prev_id, prev_precedence)) = completion {
|
||||
if TokenConflictMap::prefer_token(
|
||||
self.lexical_grammar,
|
||||
(prev_precedence, prev_id),
|
||||
(prec, id),
|
||||
)
|
||||
{
|
||||
continue;
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
completion = Some((id, prec));
|
||||
}
|
||||
|
||||
let (transitions, has_sep) = self.cursor.transitions_and_any_sep();
|
||||
let transitions = self.cursor.transitions();
|
||||
let has_sep = self.cursor.transition_chars().any(|(_, sep)| sep);
|
||||
|
||||
// If EOF is a valid lookahead token, add a transition predicated on the null
|
||||
// character that leads to the empty set of NFA states.
|
||||
if eof_valid {
|
||||
let (next_state_id, _) = self.add_state(Vec::new(), false);
|
||||
self.table.states[state_id as usize].eof_action = Some(AdvanceAction {
|
||||
self.table.states[state_id].eof_action = Some(AdvanceAction {
|
||||
state: next_state_id,
|
||||
in_main_token: true,
|
||||
});
|
||||
}
|
||||
|
||||
for transition in transitions {
|
||||
if let Some((completed_id, completed_precedence)) = completion
|
||||
&& !TokenConflictMap::prefer_transition(
|
||||
if let Some((completed_id, completed_precedence)) = completion {
|
||||
if !TokenConflictMap::prefer_transition(
|
||||
self.lexical_grammar,
|
||||
&transition,
|
||||
completed_id,
|
||||
completed_precedence,
|
||||
has_sep,
|
||||
)
|
||||
{
|
||||
continue;
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let (next_state_id, _) =
|
||||
self.add_state(transition.states, eof_valid && transition.is_separator);
|
||||
self.table.states[state_id as usize].advance_actions.push((
|
||||
self.table.states[state_id].advance_actions.push((
|
||||
transition.characters,
|
||||
AdvanceAction {
|
||||
state: next_state_id,
|
||||
|
|
@ -273,78 +273,41 @@ impl<'a> LexTableBuilder<'a> {
|
|||
}
|
||||
|
||||
if let Some((complete_id, _)) = completion {
|
||||
self.table.states[state_id as usize].accept_action =
|
||||
Some(Symbol::terminal(complete_id));
|
||||
self.table.states[state_id].accept_action = Some(Symbol::terminal(complete_id));
|
||||
} else if self.cursor.state_ids.is_empty() {
|
||||
self.table.states[state_id as usize].accept_action = Some(Symbol::end());
|
||||
self.table.states[state_id].accept_action = Some(Symbol::end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_token_conflicts(
|
||||
i: usize,
|
||||
set_without_terminal: &TokenSet,
|
||||
token_conflict_map: &TokenConflictMap,
|
||||
coincident_token_index: &CoincidentTokenIndex,
|
||||
) -> bool {
|
||||
let wpr = token_conflict_map.row_words;
|
||||
let row_start = i * wpr;
|
||||
let set_bits = set_without_terminal.terminal_bits_words();
|
||||
|
||||
// Does terminal i conflict with or match-prefix any terminal in the set?
|
||||
let conflict_row = &token_conflict_map.conflict_or_prefix_bits[row_start..row_start + wpr];
|
||||
for (&c, &s) in conflict_row.iter().zip(set_bits) {
|
||||
if c & s != 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Does terminal i overlap (in either direction) with any non-coincident terminal in the set?
|
||||
let overlap_row = &token_conflict_map.overlap_either_bits[row_start..row_start + wpr];
|
||||
let coincident_row = &coincident_token_index.row_bits[row_start..row_start + wpr];
|
||||
for ((&o, &s), &c) in overlap_row.iter().zip(set_bits).zip(coincident_row) {
|
||||
if o & s & !c != 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn merge_token_set(
|
||||
tokens: &mut TokenSet,
|
||||
other: &TokenSet,
|
||||
lexical_grammar: &LexicalGrammar,
|
||||
token_conflict_map: &TokenConflictMap,
|
||||
coincident_token_index: &CoincidentTokenIndex,
|
||||
) -> bool {
|
||||
if tokens
|
||||
.terminals()
|
||||
.filter(|terminal| !other.contains_terminal(terminal.index as usize))
|
||||
.any(|terminal| {
|
||||
check_token_conflicts(
|
||||
terminal.index as usize,
|
||||
other,
|
||||
token_conflict_map,
|
||||
coincident_token_index,
|
||||
)
|
||||
})
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for i in 0..lexical_grammar.variables.len() {
|
||||
let symbol = Symbol::terminal(i);
|
||||
let set_without_terminal = match (tokens.contains_terminal(i), other.contains_terminal(i)) {
|
||||
(true, false) => other,
|
||||
(false, true) => tokens,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
if other
|
||||
.terminals()
|
||||
.filter(|terminal| !tokens.contains_terminal(terminal.index as usize))
|
||||
.any(|terminal| {
|
||||
check_token_conflicts(
|
||||
terminal.index as usize,
|
||||
tokens,
|
||||
token_conflict_map,
|
||||
coincident_token_index,
|
||||
)
|
||||
})
|
||||
{
|
||||
return false;
|
||||
for existing_token in set_without_terminal.terminals() {
|
||||
if token_conflict_map.does_conflict(i, existing_token.index)
|
||||
|| token_conflict_map.does_match_prefix(i, existing_token.index)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if !coincident_token_index.contains(symbol, existing_token)
|
||||
&& (token_conflict_map.does_overlap(existing_token.index, i)
|
||||
|| token_conflict_map.does_overlap(i, existing_token.index))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tokens.insert_all(other);
|
||||
|
|
@ -354,7 +317,7 @@ fn merge_token_set(
|
|||
fn minimize_lex_table(table: &mut LexTable, parse_table: &mut ParseTable) {
|
||||
// Initially group the states by their accept action and their
|
||||
// valid lookahead characters.
|
||||
let mut state_ids_by_signature = FxHashMap::default();
|
||||
let mut state_ids_by_signature = HashMap::new();
|
||||
for (i, state) in table.states.iter().enumerate() {
|
||||
let signature = (
|
||||
i == 0,
|
||||
|
|
@ -369,7 +332,7 @@ fn minimize_lex_table(table: &mut LexTable, parse_table: &mut ParseTable) {
|
|||
state_ids_by_signature
|
||||
.entry(signature)
|
||||
.or_insert(Vec::new())
|
||||
.push(i as u32);
|
||||
.push(i);
|
||||
}
|
||||
let mut state_ids_by_group_id = state_ids_by_signature
|
||||
.into_iter()
|
||||
|
|
@ -382,10 +345,10 @@ fn minimize_lex_table(table: &mut LexTable, parse_table: &mut ParseTable) {
|
|||
.unwrap();
|
||||
state_ids_by_group_id.swap(error_group_index, 0);
|
||||
|
||||
let mut group_ids_by_state_id = vec![0u32; table.states.len()];
|
||||
let mut group_ids_by_state_id = vec![0; table.states.len()];
|
||||
for (group_id, state_ids) in state_ids_by_group_id.iter().enumerate() {
|
||||
for state_id in state_ids {
|
||||
group_ids_by_state_id[*state_id as usize] = group_id as u32;
|
||||
group_ids_by_state_id[*state_id] = group_id;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -400,35 +363,30 @@ fn minimize_lex_table(table: &mut LexTable, parse_table: &mut ParseTable) {
|
|||
let mut new_states = Vec::with_capacity(state_ids_by_group_id.len());
|
||||
for state_ids in &state_ids_by_group_id {
|
||||
let mut new_state = LexState::default();
|
||||
mem::swap(&mut new_state, &mut table.states[state_ids[0] as usize]);
|
||||
mem::swap(&mut new_state, &mut table.states[state_ids[0]]);
|
||||
|
||||
for (_, advance_action) in &mut new_state.advance_actions {
|
||||
advance_action.state = group_ids_by_state_id[advance_action.state as usize];
|
||||
advance_action.state = group_ids_by_state_id[advance_action.state];
|
||||
}
|
||||
if let Some(eof_action) = &mut new_state.eof_action {
|
||||
eof_action.state = group_ids_by_state_id[eof_action.state as usize];
|
||||
eof_action.state = group_ids_by_state_id[eof_action.state];
|
||||
}
|
||||
new_states.push(new_state);
|
||||
}
|
||||
|
||||
for state in &mut parse_table.states {
|
||||
state.lex_state_id = group_ids_by_state_id[state.lex_state_id as usize];
|
||||
state.lex_state_id = group_ids_by_state_id[state.lex_state_id];
|
||||
}
|
||||
|
||||
table.states = new_states;
|
||||
}
|
||||
|
||||
fn lex_states_differ(
|
||||
left: &LexState,
|
||||
right: &LexState,
|
||||
group_ids_by_state_id: &[LexStateId],
|
||||
) -> bool {
|
||||
fn lex_states_differ(left: &LexState, right: &LexState, group_ids_by_state_id: &[usize]) -> bool {
|
||||
left.advance_actions
|
||||
.iter()
|
||||
.zip(right.advance_actions.iter())
|
||||
.any(|(left, right)| {
|
||||
group_ids_by_state_id[left.1.state as usize]
|
||||
!= group_ids_by_state_id[right.1.state as usize]
|
||||
group_ids_by_state_id[left.1.state] != group_ids_by_state_id[right.1.state]
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -438,9 +396,9 @@ fn sort_states(table: &mut LexTable, parse_table: &mut ParseTable) {
|
|||
old_ids_by_new_id[1..].sort_by_key(|id| &table.states[*id]);
|
||||
|
||||
// Get the inverse mapping
|
||||
let mut new_ids_by_old_id = vec![0u32; old_ids_by_new_id.len()];
|
||||
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 as u32;
|
||||
new_ids_by_old_id[*old_id] = id;
|
||||
}
|
||||
|
||||
// Reorder the parse states and update their references to reflect
|
||||
|
|
@ -451,10 +409,10 @@ fn sort_states(table: &mut LexTable, parse_table: &mut ParseTable) {
|
|||
let mut state = LexState::default();
|
||||
mem::swap(&mut state, &mut table.states[*old_id]);
|
||||
for (_, advance_action) in &mut state.advance_actions {
|
||||
advance_action.state = new_ids_by_old_id[advance_action.state as usize];
|
||||
advance_action.state = new_ids_by_old_id[advance_action.state];
|
||||
}
|
||||
if let Some(eof_action) = &mut state.eof_action {
|
||||
eof_action.state = new_ids_by_old_id[eof_action.state as usize];
|
||||
eof_action.state = new_ids_by_old_id[eof_action.state];
|
||||
}
|
||||
state
|
||||
})
|
||||
|
|
@ -462,6 +420,6 @@ fn sort_states(table: &mut LexTable, parse_table: &mut ParseTable) {
|
|||
|
||||
// Update the parse table's lex state references
|
||||
for state in &mut parse_table.states {
|
||||
state.lex_state_id = new_ids_by_old_id[state.lex_state_id as usize];
|
||||
state.lex_state_id = new_ids_by_old_id[state.lex_state_id];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue