├── .clang-format ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── R-CMD-check.yaml │ ├── check-no-suggests.yaml │ ├── ci.yaml │ ├── format-suggest.yaml │ ├── package-crates.yaml │ ├── package-npm.yaml │ ├── pkgdown.yaml │ ├── publish.yaml │ ├── release.yaml │ └── test-coverage.yaml ├── .gitignore ├── .nvmrc ├── .vscode ├── settings.json └── tasks.json ├── CHANGELOG.md ├── CMakeLists.txt ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── Package.swift ├── README.md ├── binding.gyp ├── bindings ├── c │ ├── tree-sitter-r.h │ └── tree-sitter-r.pc.in ├── go │ ├── binding.go │ └── binding_test.go ├── node │ ├── binding.cc │ ├── binding_test.js │ ├── index.d.ts │ └── index.js ├── python │ ├── tests │ │ └── test_binding.py │ └── tree_sitter_r │ │ ├── __init__.py │ │ ├── __init__.pyi │ │ ├── binding.c │ │ └── py.typed ├── r │ ├── .Rbuildignore │ ├── .gitignore │ ├── .vscode │ │ ├── extensions.json │ │ └── settings.json │ ├── DESCRIPTION │ ├── LICENSE │ ├── LICENSE.md │ ├── LICENSE.note │ ├── NAMESPACE │ ├── NEWS.md │ ├── R │ │ ├── abi.R │ │ ├── import-standalone-language.R │ │ ├── language.R │ │ ├── treesitter.r-package.R │ │ └── zzz.R │ ├── README.Rmd │ ├── README.md │ ├── _pkgdown.yml │ ├── air.toml │ ├── bootstrap.R │ ├── codecov.yml │ ├── cran-comments.md │ ├── man │ │ ├── language.Rd │ │ └── treesitter.r-package.Rd │ ├── src │ │ ├── .gitignore │ │ ├── binding.c │ │ └── init.c │ ├── tests │ │ ├── testthat.R │ │ └── testthat │ │ │ ├── _snaps │ │ │ ├── calls.md │ │ │ ├── control-flow.md │ │ │ ├── delimiter.md │ │ │ ├── extract.md │ │ │ ├── functions.md │ │ │ ├── literals.md │ │ │ ├── miscellaneous.md │ │ │ ├── missing.md │ │ │ ├── namespace.md │ │ │ ├── newlines.md │ │ │ ├── operators.md │ │ │ ├── strings.md │ │ │ ├── unclosed.md │ │ │ └── unicode.md │ │ │ ├── fixtures │ │ │ ├── describe.scm │ │ │ └── test_that.scm │ │ │ ├── helper-print.R │ │ │ ├── helper-query-describe.R │ │ │ ├── helper-query-testthat.R │ │ │ ├── helper-query.R │ │ │ ├── references │ │ │ ├── calls.R │ │ │ ├── control-flow.R │ │ │ ├── delimiter.R │ │ │ ├── extract.R │ │ │ ├── functions.R │ │ │ ├── literals.R │ │ │ ├── miscellaneous.R │ │ │ ├── missing.R │ │ │ ├── namespace.R │ │ │ ├── newlines.R │ │ │ ├── operators.R │ │ │ ├── strings.R │ │ │ ├── unclosed.R │ │ │ └── unicode.R │ │ │ ├── test-abi.R │ │ │ ├── test-calls.R │ │ │ ├── test-control-flow.R │ │ │ ├── test-delimiter.R │ │ │ ├── test-extract.R │ │ │ ├── test-functions.R │ │ │ ├── test-literals.R │ │ │ ├── test-miscellaneous.R │ │ │ ├── test-missing.R │ │ │ ├── test-namespace.R │ │ │ ├── test-newlines.R │ │ │ ├── test-operators.R │ │ │ ├── test-query-describe.R │ │ │ ├── test-query-testthat.R │ │ │ ├── test-stack-crash.R │ │ │ ├── test-start.R │ │ │ ├── test-strings.R │ │ │ ├── test-unclosed.R │ │ │ └── test-unicode.R │ └── tools │ │ └── abi.R ├── rust │ ├── build.rs │ └── lib.rs └── swift │ ├── TreeSitterR │ └── r.h │ └── TreeSitterRTests │ └── TreeSitterRTests.swift ├── go.mod ├── grammar.js ├── package-lock.json ├── package.json ├── pyproject.toml ├── queries ├── highlights.scm ├── locals.scm └── tags.scm ├── setup.py ├── src ├── grammar.json ├── node-types.json ├── parser.c ├── scanner.c └── tree_sitter │ ├── alloc.h │ ├── array.h │ └── parser.h ├── test ├── corpus │ ├── expressions-errors.txt │ ├── expressions.txt │ └── literals.txt ├── highlight │ ├── constant.R │ ├── control.R │ ├── functions.R │ ├── literals.R │ ├── namespace.R │ ├── operators.R │ ├── punctuation.R │ └── variables.R └── tags │ ├── calls.R │ └── function-definitions.R └── tree-sitter.json /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | IndentWidth: 2 3 | DerivePointerAlignment: false 4 | PointerAlignment: Left 5 | ColumnLimit: 90 6 | AlignAfterOpenBracket: BlockIndent 7 | SpaceAfterCStyleCast: true 8 | IncludeBlocks: Regroup 9 | AllowShortFunctionsOnASingleLine: Empty 10 | BinPackArguments: false 11 | BinPackParameters: false 12 | AllowAllParametersOfDeclarationOnNextLine: false 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | 6 | [*.{json,toml,yml,gyp}] 7 | indent_style = space 8 | indent_size = 2 9 | 10 | [*.js] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.scm] 15 | indent_style = space 16 | indent_size = 2 17 | 18 | [*.{c,cc,h}] 19 | indent_style = space 20 | indent_size = 4 21 | 22 | [*.rs] 23 | indent_style = space 24 | indent_size = 4 25 | 26 | [*.{py,pyi}] 27 | indent_style = space 28 | indent_size = 4 29 | 30 | [*.swift] 31 | indent_style = space 32 | indent_size = 4 33 | 34 | [*.go] 35 | indent_style = tab 36 | indent_size = 8 37 | 38 | [Makefile] 39 | indent_style = tab 40 | indent_size = 8 41 | 42 | [parser.c] 43 | indent_size = 2 44 | 45 | [{alloc,array,parser}.h] 46 | indent_size = 2 47 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | # Generated source files 4 | src/*.json linguist-generated 5 | src/parser.c linguist-generated 6 | src/tree_sitter/* linguist-generated 7 | 8 | # C bindings 9 | bindings/c/* linguist-generated 10 | 11 | # Node.js bindings 12 | bindings/node/* linguist-generated 13 | 14 | # Python bindings 15 | bindings/python/** linguist-generated 16 | 17 | # Go bindings 18 | bindings/go/* linguist-generated 19 | 20 | # Swift bindings 21 | bindings/swift/** linguist-generated 22 | 23 | # Exclude `bindings/r` and `bindings/rust` from the set of folders 24 | # that don't automatically show diffs on GitHub. The diffs here are 25 | # typically meaningful for us right now. 26 | bindings/r/** -linguist-generated 27 | bindings/rust/** -linguist-generated 28 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | # 4 | # NOTE: This workflow is overkill for most R packages and 5 | # check-standard.yaml is likely a better choice. 6 | # usethis::use_github_action("check-standard") will install it. 7 | on: 8 | push: 9 | branches: [main, master] 10 | pull_request: 11 | branches: [main, master] 12 | 13 | name: R-CMD-check 14 | 15 | jobs: 16 | R-CMD-check: 17 | runs-on: ${{ matrix.config.os }} 18 | 19 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | config: 25 | - {os: macos-latest, r: 'release'} 26 | 27 | - {os: windows-latest, r: 'release'} 28 | 29 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 30 | - {os: ubuntu-latest, r: 'release'} 31 | - {os: ubuntu-latest, r: 'oldrel-1'} 32 | 33 | env: 34 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 35 | R_KEEP_PKG_SOURCE: yes 36 | 37 | steps: 38 | - uses: actions/checkout@v4 39 | 40 | - uses: r-lib/actions/setup-pandoc@v2 41 | 42 | - uses: r-lib/actions/setup-r@v2 43 | with: 44 | r-version: ${{ matrix.config.r }} 45 | http-user-agent: ${{ matrix.config.http-user-agent }} 46 | use-public-rspm: true 47 | 48 | - uses: r-lib/actions/setup-r-dependencies@v2 49 | with: 50 | working-directory: bindings/r 51 | extra-packages: any::rcmdcheck 52 | needs: check 53 | 54 | - uses: r-lib/actions/check-r-package@v2 55 | with: 56 | working-directory: bindings/r 57 | upload-snapshots: true 58 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 59 | -------------------------------------------------------------------------------- /.github/workflows/check-no-suggests.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | # 4 | # NOTE: This workflow only directly installs "hard" dependencies, i.e. Depends, 5 | # Imports, and LinkingTo dependencies. Notably, Suggests dependencies are never 6 | # installed, with the exception of testthat, knitr, and rmarkdown. The cache is 7 | # never used to avoid accidentally restoring a cache containing a suggested 8 | # dependency. 9 | on: 10 | push: 11 | branches: [main, master] 12 | pull_request: 13 | branches: [main, master] 14 | 15 | name: R-CMD-check-hard 16 | 17 | permissions: read-all 18 | 19 | jobs: 20 | R-CMD-check: 21 | runs-on: ${{ matrix.config.os }} 22 | 23 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 24 | 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | config: 29 | - {os: ubuntu-latest, r: 'release'} 30 | 31 | env: 32 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 33 | R_KEEP_PKG_SOURCE: yes 34 | 35 | steps: 36 | - uses: actions/checkout@v4 37 | 38 | - uses: r-lib/actions/setup-pandoc@v2 39 | 40 | - uses: r-lib/actions/setup-r@v2 41 | with: 42 | r-version: ${{ matrix.config.r }} 43 | http-user-agent: ${{ matrix.config.http-user-agent }} 44 | use-public-rspm: true 45 | 46 | - uses: r-lib/actions/setup-r-dependencies@v2 47 | with: 48 | working-directory: bindings/r 49 | dependencies: '"hard"' 50 | cache: false 51 | extra-packages: | 52 | any::rcmdcheck 53 | any::testthat 54 | any::knitr 55 | any::rmarkdown 56 | needs: check 57 | 58 | - uses: r-lib/actions/check-r-package@v2 59 | with: 60 | working-directory: bindings/r 61 | upload-snapshots: true 62 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 63 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | concurrency: 10 | group: ${{github.workflow}}-${{github.ref}} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | test: 15 | name: Test parser 16 | runs-on: ${{matrix.os}} 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | os: [ubuntu-latest, windows-latest, macos-latest] 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v4 24 | 25 | - name: Set up tree-sitter 26 | uses: tree-sitter/setup-action/cli@v2 27 | with: 28 | tree-sitter-ref: v0.24.7 29 | 30 | - name: Run parser and binding tests (tree-sitter test) 31 | uses: tree-sitter/parser-test-action@v2 32 | with: 33 | # For a regression test in `lib.rs` 34 | test-rust: true 35 | -------------------------------------------------------------------------------- /.github/workflows/format-suggest.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/posit-dev/setup-air/tree/main/examples 2 | on: 3 | pull_request: 4 | 5 | name: format-suggest.yaml 6 | 7 | permissions: read-all 8 | 9 | jobs: 10 | format-suggest: 11 | name: format-suggest 12 | runs-on: ubuntu-latest 13 | permissions: 14 | pull-requests: write 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Install 19 | uses: posit-dev/setup-air@v1 20 | 21 | - name: Format 22 | run: air format bindings/r 23 | 24 | - name: Suggest 25 | uses: reviewdog/action-suggester@v1 26 | with: 27 | level: error 28 | fail_level: error 29 | tool_name: air 30 | -------------------------------------------------------------------------------- /.github/workflows/package-crates.yaml: -------------------------------------------------------------------------------- 1 | # https://github.com/tree-sitter/workflows/blob/main/.github/workflows/package-crates.yml 2 | 3 | name: Publish package (crates.io) 4 | 5 | on: 6 | workflow_call: 7 | inputs: 8 | package-name: 9 | description: The name of the package 10 | default: ${{github.event.repository.name}} 11 | type: string 12 | environment-name: 13 | description: The name of the environment 14 | default: crates 15 | type: string 16 | rust-toolchain: 17 | description: The Rust toolchain 18 | default: ${{vars.RUST_TOOLCHAIN || 'stable'}} 19 | type: string 20 | generate: 21 | description: Generate the parser artifacts 22 | default: false 23 | type: boolean 24 | abi-version: 25 | description: The tree-sitter ABI version 26 | default: "15" 27 | type: string 28 | secrets: 29 | CARGO_REGISTRY_TOKEN: 30 | description: An authentication token for crates.io 31 | required: true 32 | 33 | jobs: 34 | package: 35 | name: Publish Rust package 36 | runs-on: ubuntu-latest 37 | environment: 38 | name: ${{inputs.environment-name}} 39 | url: https://crates.io/crates/${{inputs.package-name}} 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v4 43 | - name: Set up Rust 44 | uses: actions-rust-lang/setup-rust-toolchain@v1 45 | with: 46 | toolchain: ${{inputs.rust-toolchain}} 47 | - name: Set up tree-sitter CLI 48 | if: ${{inputs.generate}} 49 | uses: tree-sitter/setup-action/cli@v2 50 | - name: Install dependencies 51 | run: |- 52 | JQ_SCRIPT='.dependencies | del(."node-addon-api", ."node-gyp-build") | length > 0' 53 | if jq -e "$JQ_SCRIPT" package.json >/dev/null; then 54 | npm i --ignore-scripts --omit dev --omit peer --omit optional 55 | fi 56 | - name: Regenerate parser 57 | if: ${{inputs.generate}} 58 | run: | 59 | while read -r grammar; do 60 | grammar_dir=$(dirname "$grammar") 61 | cd "$grammar_dir" 62 | tree-sitter generate 63 | cd - > /dev/null 64 | done < <(find . -name grammar.js -not -path './node_modules/*' -not -path './.build/*') 65 | env: 66 | TREE_SITTER_ABI_VERSION: ${{inputs.abi-version}} 67 | - name: Publish to crates.io 68 | run: cargo publish 69 | env: 70 | CARGO_REGISTRY_TOKEN: ${{secrets.CARGO_REGISTRY_TOKEN}} 71 | -------------------------------------------------------------------------------- /.github/workflows/package-npm.yaml: -------------------------------------------------------------------------------- 1 | # https://github.com/tree-sitter/workflows/blob/main/.github/workflows/package-npm.yml 2 | 3 | name: Publish package (npm) 4 | 5 | on: 6 | workflow_call: 7 | inputs: 8 | package-name: 9 | description: The name of the package 10 | default: ${{github.event.repository.name}} 11 | type: string 12 | environment-name: 13 | description: The name of the environment 14 | default: npm 15 | type: string 16 | node-version: 17 | description: The NodeJS version 18 | default: ${{vars.NODE_VERSION || '22'}} 19 | type: string 20 | emscripten-version: 21 | description: The Emscripten version 22 | default: ${{vars.EMSCRIPTEN_VERSION || '3.1.64'}} 23 | type: string 24 | ubuntu-version: 25 | description: The version of the Ubuntu runner image 26 | default: ${{vars.UBUNTU_VERSION || '22.04'}} 27 | type: string 28 | generate: 29 | description: Generate the parser artifacts 30 | default: false 31 | type: boolean 32 | abi-version: 33 | description: The tree-sitter ABI version 34 | default: "15" 35 | type: string 36 | secrets: 37 | NODE_AUTH_TOKEN: 38 | description: An authentication token for npm 39 | required: true 40 | 41 | defaults: 42 | run: 43 | shell: bash 44 | 45 | jobs: 46 | build_wasm: 47 | name: Build Wasm binaries 48 | runs-on: ubuntu-${{inputs.ubuntu-version}} 49 | continue-on-error: true 50 | steps: 51 | - name: Checkout repository 52 | uses: actions/checkout@v4 53 | - name: Set up NodeJS 54 | uses: actions/setup-node@v4 55 | with: 56 | cache: npm 57 | node-version: ${{inputs.node-version}} 58 | - name: Set up Emscripten 59 | uses: mymindstorm/setup-emsdk@v14 60 | with: 61 | version: ${{inputs.emscripten-version}} 62 | - name: Install dependencies 63 | run: npm i --omit peer --omit optional 64 | - name: Regenerate parser 65 | if: ${{inputs.generate}} 66 | run: | 67 | while read -r grammar; do 68 | grammar_dir=$(dirname "$grammar") 69 | cd "$grammar_dir" 70 | npm x -- tree-sitter generate 71 | cd - > /dev/null 72 | done < <(find . -name grammar.js -not -path './node_modules/*' -not -path './.build/*') 73 | env: 74 | TREE_SITTER_ABI_VERSION: ${{inputs.abi-version}} 75 | - name: Build Wasm binaries 76 | run: |- 77 | while read -r grammar; do 78 | npm x -- tree-sitter build --wasm "${grammar%/grammar.js}" 79 | done < <(find . -name grammar.js -not -path './node_modules/*') 80 | - name: Upload binaries 81 | uses: actions/upload-artifact@v4 82 | with: 83 | path: "*.wasm" 84 | name: prebuilds-Wasm 85 | retention-days: 2 86 | 87 | build_node: 88 | name: Build NodeJS binaries on ${{matrix.os}} 89 | runs-on: ${{matrix.os}} 90 | strategy: 91 | matrix: 92 | os: 93 | - "ubuntu-${{inputs.ubuntu-version}}" 94 | - windows-latest 95 | - macos-latest 96 | steps: 97 | - name: Checkout repository 98 | uses: actions/checkout@v4 99 | - name: Set up NodeJS 100 | uses: actions/setup-node@v4 101 | with: 102 | cache: npm 103 | node-version: ${{inputs.node-version}} 104 | - name: Install dependencies 105 | run: npm i --omit peer --omit optional 106 | - name: Regenerate parser 107 | if: ${{inputs.generate}} 108 | run: | 109 | while read -r grammar; do 110 | grammar_dir=$(dirname "$grammar") 111 | cd "$grammar_dir" 112 | npm x -- tree-sitter generate 113 | cd - > /dev/null 114 | done < <(find . -name grammar.js -not -path './node_modules/*' -not -path './.build/*') 115 | - name: Build x64 binary 116 | run: npm x -- prebuildify --arch x64 -t 20.9.0 117 | - name: Set up cross-compilation 118 | if: runner.os == 'Linux' 119 | run: |- 120 | sudo apt-get update 121 | sudo apt-get install g++-aarch64-linux-gnu 122 | printf '%s\n' >> "$GITHUB_ENV" \ 123 | 'CC=aarch64-linux-gnu-gcc' 'CXX=aarch64-linux-gnu-g++' 124 | - name: Build arm64 binary 125 | run: npm x -- prebuildify --arch arm64 -t 20.9.0 126 | - name: Upload binaries 127 | uses: actions/upload-artifact@v4 128 | with: 129 | path: prebuilds/** 130 | name: prebuilds-${{runner.os}} 131 | retention-days: 2 132 | 133 | package: 134 | name: Publish NodeJS package 135 | needs: [build_wasm, build_node] 136 | runs-on: ubuntu-${{inputs.ubuntu-version}} 137 | environment: 138 | name: ${{inputs.environment-name}} 139 | # Changed so it uses the scoped url 140 | url: https://www.npmjs.com/package/@davisvaughan/${{inputs.package-name}} 141 | # url: https://www.npmjs.com/package/${{inputs.package-name}} 142 | steps: 143 | - name: Checkout repository 144 | uses: actions/checkout@v4 145 | - name: Set up NodeJS 146 | uses: actions/setup-node@v4 147 | with: 148 | cache: npm 149 | node-version: ${{inputs.node-version}} 150 | registry-url: https://registry.npmjs.org/ 151 | - name: Download binaries 152 | uses: actions/download-artifact@v4 153 | with: 154 | path: prebuilds 155 | pattern: prebuilds-* 156 | merge-multiple: true 157 | - name: Check binaries 158 | run: tree prebuilds 159 | - name: Move Wasm binaries to root 160 | continue-on-error: true 161 | run: mv -v prebuilds/*.wasm . 162 | - name: Publish to npm 163 | # This is the only thing we change, because we currently publish a scoped package, 164 | # so we need `--access public` because scoped packages are private by default (#41) 165 | run: npm publish --access public 166 | # run: npm publish 167 | env: 168 | NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}} 169 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | release: 9 | types: [published] 10 | workflow_dispatch: 11 | 12 | name: pkgdown 13 | 14 | jobs: 15 | pkgdown: 16 | runs-on: ubuntu-latest 17 | # Only restrict concurrency for non-PR jobs 18 | concurrency: 19 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 20 | env: 21 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 22 | permissions: 23 | contents: write 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | - uses: r-lib/actions/setup-pandoc@v2 28 | 29 | - uses: r-lib/actions/setup-r@v2 30 | with: 31 | use-public-rspm: true 32 | 33 | - name: Bootstrap 34 | run: | 35 | source("bootstrap.R") 36 | shell: Rscript {0} 37 | working-directory: bindings/r 38 | 39 | - uses: r-lib/actions/setup-r-dependencies@v2 40 | with: 41 | working-directory: bindings/r 42 | extra-packages: any::pkgdown, local::. 43 | needs: website 44 | 45 | - name: Build site 46 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 47 | shell: Rscript {0} 48 | working-directory: bindings/r 49 | 50 | - name: Deploy to GitHub pages 🚀 51 | if: github.event_name != 'pull_request' 52 | uses: JamesIves/github-pages-deploy-action@v4.5.0 53 | with: 54 | clean: false 55 | branch: gh-pages 56 | folder: bindings/r/docs 57 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish packages 2 | 3 | on: 4 | push: 5 | tags: ["*"] 6 | 7 | permissions: 8 | contents: write 9 | id-token: write 10 | attestations: write 11 | 12 | jobs: 13 | github: 14 | uses: ./.github/workflows/release.yaml 15 | # uses: tree-sitter/workflows/.github/workflows/release.yml@main 16 | with: 17 | generate: false 18 | attestations: true 19 | abi-version: "14" 20 | npm: 21 | uses: ./.github/workflows/package-npm.yaml 22 | # uses: tree-sitter/workflows/.github/workflows/package-npm.yml@main 23 | secrets: 24 | NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}} 25 | with: 26 | generate: false 27 | abi-version: "14" 28 | crates: 29 | uses: ./.github/workflows/package-crates.yaml 30 | # uses: tree-sitter/workflows/.github/workflows/package-crates.yml@main 31 | secrets: 32 | CARGO_REGISTRY_TOKEN: ${{secrets.CARGO_REGISTRY_TOKEN}} 33 | with: 34 | generate: false 35 | abi-version: "14" 36 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | # https://github.com/tree-sitter/workflows/blob/main/.github/workflows/release.yml 2 | 3 | name: Publish GitHub release 4 | 5 | on: 6 | workflow_call: 7 | inputs: 8 | emscripten-version: 9 | description: The Emscripten version 10 | default: ${{vars.EMSCRIPTEN_VERSION || '3.1.64'}} 11 | type: string 12 | generate: 13 | description: Generate the parser artifacts 14 | default: false 15 | type: boolean 16 | attestations: 17 | description: Generate attestations for artifacts 18 | default: false 19 | type: boolean 20 | abi-version: 21 | description: The tree-sitter ABI version 22 | default: "15" 23 | type: string 24 | 25 | permissions: 26 | contents: write 27 | id-token: write 28 | attestations: write 29 | 30 | jobs: 31 | release: 32 | name: Build release artifacts 33 | runs-on: ubuntu-latest 34 | env: 35 | REPO_NAME: ${{github.event.repository.name}} 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v4 39 | - name: Set up Emscripten 40 | uses: mymindstorm/setup-emsdk@v14 41 | with: 42 | version: ${{inputs.emscripten-version}} 43 | - name: Set up tree-sitter CLI 44 | uses: tree-sitter/setup-action/cli@v2 45 | - name: Install parser dependencies 46 | run: |- 47 | JQ_SCRIPT='.dependencies | del(."node-addon-api", ."node-gyp-build") | length > 0' 48 | if jq -e "$JQ_SCRIPT" package.json >/dev/null; then 49 | npm i --ignore-scripts --omit dev --omit peer --omit optional 50 | fi 51 | - name: Generate parser 52 | if: inputs.generate 53 | shell: bash 54 | run: | 55 | while read -r grammar; do 56 | grammar_dir=$(dirname "$grammar") 57 | pushd "$grammar_dir" 58 | tree-sitter generate 59 | popd > /dev/null 60 | done < <(find . -name grammar.js -not -path './node_modules/*') 61 | env: 62 | TREE_SITTER_ABI_VERSION: ${{inputs.abi-version}} 63 | - name: Build Wasm binaries 64 | shell: bash 65 | run: |- 66 | while read -r grammar; do 67 | tree-sitter build --wasm "${grammar%/grammar.js}" 68 | done < <(find . -name grammar.js -not -path './node_modules/*') 69 | - name: Create source code tarball 70 | run: | 71 | git ls-files > "$RUNNER_TEMP/files" 72 | while read -r grammar; do 73 | : "$(dirname "$grammar")"; src_dir="${_/.\//}/src" 74 | printf '%s\n' >> "$RUNNER_TEMP/files" \ 75 | "$src_dir"/parser.c "$src_dir"/grammar.json \ 76 | "$src_dir"/node-types.json "$src_dir"/tree_sitter/* 77 | done < <(find . -name grammar.js -not -path './node_modules/*') 78 | tar cvzf "$REPO_NAME.tar.gz" -T <(sort -u "$RUNNER_TEMP/files") 79 | - name: Generate attestations 80 | uses: actions/attest-build-provenance@v2 81 | if: inputs.attestations 82 | with: 83 | subject-path: | 84 | *.wasm 85 | ${{env.REPO_NAME}}.tar.gz 86 | - name: Create GitHub release 87 | run: |- 88 | gh release create "$GITHUB_REF_NAME" *.wasm "$REPO_NAME.tar.gz" \ 89 | -n "**NOTE:** Download \`$REPO_NAME.tar.gz\` for the *complete* source code." 90 | env: 91 | GH_TOKEN: ${{github.token}} 92 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: test-coverage 10 | 11 | jobs: 12 | test-coverage: 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - uses: r-lib/actions/setup-r@v2 21 | with: 22 | use-public-rspm: true 23 | 24 | - uses: r-lib/actions/setup-r-dependencies@v2 25 | with: 26 | working-directory: bindings/r 27 | extra-packages: any::covr 28 | needs: coverage 29 | 30 | - name: Bootstrap 31 | run: | 32 | source("bootstrap.R") 33 | shell: Rscript {0} 34 | working-directory: bindings/r 35 | 36 | - name: Test coverage 37 | run: | 38 | covr::codecov( 39 | quiet = FALSE, 40 | clean = FALSE, 41 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 42 | ) 43 | shell: Rscript {0} 44 | working-directory: bindings/r 45 | 46 | - name: Show testthat output 47 | if: always() 48 | run: | 49 | ## -------------------------------------------------------------------- 50 | find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 51 | shell: bash 52 | 53 | - name: Upload test results 54 | if: failure() 55 | uses: actions/upload-artifact@v4 56 | with: 57 | name: coverage-test-failures 58 | path: ${{ runner.temp }}/package 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rust artifacts 2 | target/ 3 | 4 | # Node artifacts 5 | build/ 6 | prebuilds/ 7 | node_modules/ 8 | 9 | # Swift artifacts 10 | .build/ 11 | 12 | # Go artifacts 13 | _obj/ 14 | 15 | # Python artifacts 16 | .venv/ 17 | dist/ 18 | *.egg-info 19 | *.whl 20 | 21 | # R artifacts 22 | *.Rcheck/ 23 | *.tar.gz 24 | 25 | # C artifacts 26 | *.a 27 | *.so 28 | *.so.* 29 | *.dylib 30 | *.dll 31 | *.pc 32 | 33 | # Example dirs 34 | /examples/*/ 35 | 36 | # Grammar volatiles 37 | *.wasm 38 | *.obj 39 | *.o 40 | 41 | # Archives 42 | *.tar.gz 43 | *.tgz 44 | *.zip 45 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v22.16.0 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[c]": { 3 | "editor.formatOnSave": true, 4 | "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd" 5 | }, 6 | "[plaintext]": { 7 | "editor.tabSize": 2, 8 | "editor.insertSpaces": true 9 | }, 10 | "[javascript]": { 11 | "editor.tabSize": 2, 12 | "editor.insertSpaces": true 13 | }, 14 | "files.trimTrailingWhitespace": true, 15 | "files.trimFinalNewlines": true, 16 | "files.insertFinalNewline": true, 17 | "scm.showHistoryGraph": false 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Test Grammar", 6 | "type": "shell", 7 | "command": "tree-sitter generate && tree-sitter test", 8 | "group": { 9 | "kind": "build", 10 | "isDefault": true 11 | }, 12 | "osx": { 13 | "options": { 14 | "env": { 15 | "PATH": "/opt/homebrew/bin:${PATH}", 16 | "CC": "/usr/bin/clang", 17 | "CXX": "/usr/bin/clang++" 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## devel 4 | 5 | ## 1.2.0 6 | 7 | - Added `"open"` and `"close"` fields to single and double quoted `string`s (#139). 8 | 9 | - Fixed an issue related to closing brackets in raw strings (#162). 10 | 11 | - Binary exponents are now supported in hexadecimal constants (#159). 12 | 13 | - `NULL` is now allowed as a function call argument name (#164). 14 | 15 | - `...` and `..1` are now supported in more places, such as `x$...` and `x@..1` (#148). 16 | 17 | - `fn(a b)` is now correctly parsed as an error rather than as two sequential arguments (#140). 18 | 19 | - Fixed another issue where the `program` node didn't start at `(0, 0)` if there was leading whitespace before the first token (#151). 20 | 21 | - Newlines after `function` but before the `()` are again allowed (#145). 22 | 23 | - Newlines are now allowed after an `else` but before the `alternative` (#141). 24 | 25 | - `parenthesized_expression` has been simplified to better align with R's parser. Specifically, it now expects exactly 1 required `body` expression, rather than allowing zero or more optional expressions (#144). 26 | 27 | - `highlights.scm` now includes `!` as an `@operator`. 28 | 29 | - `tags.scm` now tags function definitions with a string name as `@definition.function` (#147, @MichaelChirico). 30 | 31 | - Removed an unnecessary `optional()` from `_parameter_with_default` (#161). 32 | 33 | - The internal `_hex_literal` rule was simplified slightly (#138). 34 | 35 | - Changed a number of internal files to match tree-sitter v0.24.7 recommendations (#169). 36 | 37 | ## 1.1.0 38 | 39 | - Switched to using `tree-sitter-language` in the Rust bindings to remove a dependency on a specific `tree-sitter` crate version (#133). 40 | 41 | - Fixed an issue where the `program` node didn't always start at `(0, 0)` (#134). 42 | 43 | - To align better with the R grammar and to greatly simplify the possible states in the tree-sitter grammar, some fields are no longer optional (#132). 44 | - The `"body"` field of `function_definition`, `for_statement`, `while_statement`, and `repeat_statement`. 45 | - The `"close"` field of `braced_expression` and `parenthesized_expression`. 46 | 47 | ## 1.0.1 48 | 49 | - Changed tree-sitter dependency from `0.22.6` to `>=0.21.0` to match other grammars and be less restrictive. 50 | 51 | ## 1.0.0 52 | 53 | - This release incorporates a complete rewrite of the grammar which has been incubating on the `next` branch for the past two years. It contains many bug fixes and improvements. If you still need to use the previous version, you can pin to the `main-old` branch, which is the commit just before the `next` branch was merged in. 54 | 55 | - Added a `CHANGELOG.md` to track changes. 56 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | project(tree-sitter-r 4 | VERSION "1.2.0" 5 | DESCRIPTION "R grammar for tree-sitter" 6 | HOMEPAGE_URL "https://github.com/r-lib/tree-sitter-r" 7 | LANGUAGES C) 8 | 9 | option(BUILD_SHARED_LIBS "Build using shared libraries" ON) 10 | option(TREE_SITTER_REUSE_ALLOCATOR "Reuse the library allocator" OFF) 11 | 12 | set(TREE_SITTER_ABI_VERSION 14 CACHE STRING "Tree-sitter ABI version") 13 | if(NOT ${TREE_SITTER_ABI_VERSION} MATCHES "^[0-9]+$") 14 | unset(TREE_SITTER_ABI_VERSION CACHE) 15 | message(FATAL_ERROR "TREE_SITTER_ABI_VERSION must be an integer") 16 | endif() 17 | 18 | find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI") 19 | 20 | add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c" 21 | DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json" 22 | COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json 23 | --abi=${TREE_SITTER_ABI_VERSION} 24 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 25 | COMMENT "Generating parser.c") 26 | 27 | add_library(tree-sitter-r src/parser.c) 28 | if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/scanner.c) 29 | target_sources(tree-sitter-r PRIVATE src/scanner.c) 30 | endif() 31 | target_include_directories(tree-sitter-r PRIVATE src) 32 | 33 | target_compile_definitions(tree-sitter-r PRIVATE 34 | $<$:TREE_SITTER_REUSE_ALLOCATOR> 35 | $<$:TREE_SITTER_DEBUG>) 36 | 37 | set_target_properties(tree-sitter-r 38 | PROPERTIES 39 | C_STANDARD 11 40 | POSITION_INDEPENDENT_CODE ON 41 | SOVERSION "${TREE_SITTER_ABI_VERSION}.${PROJECT_VERSION_MAJOR}" 42 | DEFINE_SYMBOL "") 43 | 44 | configure_file(bindings/c/tree-sitter-r.pc.in 45 | "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-r.pc" @ONLY) 46 | 47 | include(GNUInstallDirs) 48 | 49 | install(FILES bindings/c/tree-sitter-r.h 50 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/tree_sitter") 51 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-r.pc" 52 | DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig") 53 | install(TARGETS tree-sitter-r 54 | LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}") 55 | 56 | add_custom_target(ts-test "${TREE_SITTER_CLI}" test 57 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 58 | COMMENT "tree-sitter test") 59 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Release process 2 | 3 | ## Version 4 | 5 | Unlike some other tree-sitter grammars, the R grammar's release version is not tied to the tree-sitter version. Instead, we just use standard semantic versioning. We released 1.0.0 when we merged the longstanding `next` branch into `main`, and then we froze `main-old` for people to pin to if they are slow to update. 6 | 7 | ## Flow 8 | 9 | - Create an `rc/x-y-z` branch 10 | 11 | - Update all binding versions to the new version 12 | 13 | - Polish CHANGELOG 14 | 15 | - Update CHANGELOG's `## devel` to the new version, go ahead and add a new `## devel` header 16 | 17 | - Sync `package-npm.yaml`, `package-crates.yaml`, and `release.yaml` with [upstream tree-sitter versions](https://github.com/tree-sitter/workflows/blob/main/.github/workflows). 18 | 19 | - Push and open a PR 20 | 21 | - Follow the release procedure for R, as it is manual 22 | 23 | - Merge the PR 24 | 25 | - After merging, do NOT call `usethis::use_github_release()`. We are going to create our own git tag 26 | 27 | - Create an push a git tag for the version (the leading `v` does matter): 28 | 29 | ``` 30 | git tag vX.Y.Z 31 | git push upstream tag vX.Y.Z 32 | ``` 33 | 34 | - This will kick off `publish.yaml` for the GitHub Release, the npm package release, and the Rust crate release 35 | 36 | ## R package 37 | 38 | 39 | 40 | Davis is the maintainer. 41 | 42 | The {treesitter.r} package is relatively simple. It just provides `language()` as an external pointer into the C level tree-sitter-r grammar struct. 43 | 44 | - Decide if you also need to release [{treesitter}](https://github.com/DavisVaughan/r-tree-sitter). 45 | - Ensure you `devtools::load_all()` twice to ensure that `bootstrap.R` runs and pulls updated files into `src/` and updates any ABI changes. 46 | - Update `NEWS.md` to mention the update and point to the changelog. 47 | - Run `devtools::check()` to ensure tests are still passing. 48 | - Run `devtools::release()` to do a standard R package release if all looks good. 49 | - Use the same version as the Rust crate and the npm package. 50 | 51 | If in {treesitter} you have to deal with a bump in the minimal ABI supported by tree-sitter, then you'll definitely have to do a release of the {treesitter.r} package as well. There are tools in `abi.R` that will need to be updated with the ABI that tree-sitter-r's parser was generated with, but `bootstrap.R` will do this for you. The worst case would be if tree-sitter bumped their ABI version to X and also raised the *minimal* ABI version to X as well, due to some serious breaking change. If that happens, then you'll have to release both {treesitter} and {treesitter.r} as close together as possible, because {treesitter} will complain that it isn't compatible with the grammar from {treesitter.r}. 52 | 53 | It's worth noting that {treesitter.r} does not actually Import {treesitter}, it just Suggests it. This is generally preferred in the tree-sitter ecosystem. {treesitter} just knows how to consume the external pointer that {treesitter.r} creates. 54 | 55 | ## Rust crate 56 | 57 | 58 | 59 | Davis is the main owner of the crate, but Kevin also has publish rights. 60 | 61 | - Use `tree-sitter init --update` to update language specific files, in particular `Cargo.toml`, `bindings/rust/lib.rs` and `bindings/rust/build.rs`. 62 | 63 | - Note that tree-sitter-r only depends on `tree-sitter-language`, not the `tree-sitter` crate. 64 | 65 | - The `build.rs` script will compile in `parser.c` and `scanner.c` on a crate build. This gives `lib.rs` access to `fn tree_sitter_r() -> *const ()`, which then becomes the `LanguageFn` defined in `tree-sitter-language`. 66 | 67 | - Don't forget to update the version in `Cargo.toml` to the same version used in the R package. 68 | 69 | - `cargo publish --dry-run` will do a dry run before submitting 70 | 71 | - The `publish.yaml` CI will actually run `cargo publish` for you. It runs [this workflow](https://github.com/r-lib/tree-sitter-r/blob/main/.github/workflows/package-crates.yaml) which is our own copy of [this tree-sitter workflow](https://github.com/tree-sitter/workflows/blob/main/.github/workflows/package-crates.yml). We have our own copy of the workflow so that we are in control of updates to it, but we should try and sync it every time we do a release. Note that we have a `CARGO_REGISTRY_TOKEN` token that expires after 1 year and may need to be refreshed. 72 | 73 | ## Npm package 74 | 75 | Davis is the maintainer of the package. 76 | 77 | Note that we currently publish the package under `@davisvaughan/tree-sitter-r` as a *scoped* package, because we are waiting on the npm team to transfer ownership of the `tree-sitter-r` package (it was created maliciously by some bad actor and was taken down, and is owned by the security team for now). We have had to modify `package-npm.yaml` to account for this. 78 | 79 | We use [nvm](https://github.com/nvm-sh/nvm) for npm version management. The npm version is specified in `.nvmrc`. 80 | 81 | - Use `tree-sitter init --update` to update language specific files, in particular `package.json`, `bindings/node/*`. 82 | 83 | - For the `peerDependencies` of `tree-sitter`, we typically use the same version that tree-sitter-rust's npm bindings use, if it seems like it makes sense. 84 | 85 | - To test the bindings in a basic way 86 | 87 | ``` 88 | # To respect .nvmrc 89 | nvm use 90 | npm install 91 | # If tree-sitter doesn't get installed 92 | # npm install tree-sitter --save-dev 93 | npm test 94 | ``` 95 | 96 | - If you need to reset the node package cache, delete `node_modules/` 97 | 98 | - `npm publish --dry-run` will do a dry run 99 | 100 | - The `publish.yaml` CI will actually run `npm publish` for you. It runs [this workflow](https://github.com/r-lib/tree-sitter-r/blob/main/.github/workflows/package-npm.yaml) which is our own copy of [this tree-sitter workflow](https://github.com/tree-sitter/workflows/blob/main/.github/workflows/package-npm.yml). We have our own copy of the workflow so that we are in control of updates to it, but we should try and sync it every time we do a release. Note that we have a `NODE_AUTH_TOKEN` token that expires after 1 year and may need to be refreshed. 101 | 102 | - Note that this builds the WASM binary along with "prebuilds" for each platform, so it does more than just `npm publish`. 103 | 104 | ## GitHub Release 105 | 106 | - `publish.yaml` will kick off [this `release.yaml` workflow](https://github.com/r-lib/tree-sitter-r/blob/main/.github/workflows/release.yaml), which is our own copy of [this tree-sitter workflow](https://github.com/tree-sitter/workflows/blob/main/.github/workflows/release.yml). 107 | 108 | - `release.yaml` creates a GitHub Release containing the source code and the WASM binary 109 | 110 | - Note that `release.yaml` must be triggered by `publish.yaml` through a tag update to work correctly, because it creates the GitHub Release by using the `GITHUB_REF_NAME` variable, which should point to the tag version. 111 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "cc" 16 | version = "1.2.24" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" 19 | dependencies = [ 20 | "shlex", 21 | ] 22 | 23 | [[package]] 24 | name = "memchr" 25 | version = "2.7.4" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 28 | 29 | [[package]] 30 | name = "regex" 31 | version = "1.11.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 34 | dependencies = [ 35 | "aho-corasick", 36 | "memchr", 37 | "regex-automata", 38 | "regex-syntax", 39 | ] 40 | 41 | [[package]] 42 | name = "regex-automata" 43 | version = "0.4.9" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 46 | dependencies = [ 47 | "aho-corasick", 48 | "memchr", 49 | "regex-syntax", 50 | ] 51 | 52 | [[package]] 53 | name = "regex-syntax" 54 | version = "0.8.5" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 57 | 58 | [[package]] 59 | name = "shlex" 60 | version = "1.3.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 63 | 64 | [[package]] 65 | name = "streaming-iterator" 66 | version = "0.1.9" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" 69 | 70 | [[package]] 71 | name = "tree-sitter" 72 | version = "0.24.7" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "a5387dffa7ffc7d2dae12b50c6f7aab8ff79d6210147c6613561fc3d474c6f75" 75 | dependencies = [ 76 | "cc", 77 | "regex", 78 | "regex-syntax", 79 | "streaming-iterator", 80 | "tree-sitter-language", 81 | ] 82 | 83 | [[package]] 84 | name = "tree-sitter-language" 85 | version = "0.1.5" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "c4013970217383f67b18aef68f6fb2e8d409bc5755227092d32efb0422ba24b8" 88 | 89 | [[package]] 90 | name = "tree-sitter-r" 91 | version = "1.2.0" 92 | dependencies = [ 93 | "cc", 94 | "tree-sitter", 95 | "tree-sitter-language", 96 | ] 97 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tree-sitter-r" 3 | description = "R grammar for tree-sitter" 4 | version = "1.2.0" 5 | license = "MIT" 6 | readme = "README.md" 7 | keywords = ["incremental", "parsing", "tree-sitter", "r"] 8 | categories = ["parsing", "text-editors"] 9 | repository = "https://github.com/r-lib/tree-sitter-r" 10 | authors = [ 11 | "Kevin Ushey ", 12 | "Jim Hester ", 13 | "Davis Vaughan ", 14 | ] 15 | edition = "2021" 16 | autoexamples = false 17 | 18 | build = "bindings/rust/build.rs" 19 | include = [ 20 | "bindings/rust/*", 21 | "grammar.js", 22 | "queries/*", 23 | "src/*", 24 | "tree-sitter.json", 25 | ] 26 | 27 | [lib] 28 | path = "bindings/rust/lib.rs" 29 | 30 | [dependencies] 31 | tree-sitter-language = "0.1" 32 | 33 | [build-dependencies] 34 | cc = "1.1.22" 35 | 36 | [dev-dependencies] 37 | tree-sitter = "0.24.7" 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 tree-sitter-r authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(OS),Windows_NT) 2 | $(error Windows is not supported) 3 | endif 4 | 5 | LANGUAGE_NAME := tree-sitter-r 6 | HOMEPAGE_URL := https://github.com/r-lib/tree-sitter-r 7 | VERSION := 1.2.0 8 | 9 | # repository 10 | SRC_DIR := src 11 | 12 | TS ?= tree-sitter 13 | 14 | # install directory layout 15 | PREFIX ?= /usr/local 16 | INCLUDEDIR ?= $(PREFIX)/include 17 | LIBDIR ?= $(PREFIX)/lib 18 | PCLIBDIR ?= $(LIBDIR)/pkgconfig 19 | 20 | # source/object files 21 | PARSER := $(SRC_DIR)/parser.c 22 | EXTRAS := $(filter-out $(PARSER),$(wildcard $(SRC_DIR)/*.c)) 23 | OBJS := $(patsubst %.c,%.o,$(PARSER) $(EXTRAS)) 24 | 25 | # flags 26 | ARFLAGS ?= rcs 27 | override CFLAGS += -I$(SRC_DIR) -std=c11 -fPIC 28 | 29 | # ABI versioning 30 | SONAME_MAJOR = $(shell sed -n 's/\#define LANGUAGE_VERSION //p' $(PARSER)) 31 | SONAME_MINOR = $(word 1,$(subst ., ,$(VERSION))) 32 | 33 | # OS-specific bits 34 | ifeq ($(shell uname),Darwin) 35 | SOEXT = dylib 36 | SOEXTVER_MAJOR = $(SONAME_MAJOR).$(SOEXT) 37 | SOEXTVER = $(SONAME_MAJOR).$(SONAME_MINOR).$(SOEXT) 38 | LINKSHARED = -dynamiclib -Wl,-install_name,$(LIBDIR)/lib$(LANGUAGE_NAME).$(SOEXTVER),-rpath,@executable_path/../Frameworks 39 | else 40 | SOEXT = so 41 | SOEXTVER_MAJOR = $(SOEXT).$(SONAME_MAJOR) 42 | SOEXTVER = $(SOEXT).$(SONAME_MAJOR).$(SONAME_MINOR) 43 | LINKSHARED = -shared -Wl,-soname,lib$(LANGUAGE_NAME).$(SOEXTVER) 44 | endif 45 | ifneq ($(filter $(shell uname),FreeBSD NetBSD DragonFly),) 46 | PCLIBDIR := $(PREFIX)/libdata/pkgconfig 47 | endif 48 | 49 | all: lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) $(LANGUAGE_NAME).pc 50 | 51 | lib$(LANGUAGE_NAME).a: $(OBJS) 52 | $(AR) $(ARFLAGS) $@ $^ 53 | 54 | lib$(LANGUAGE_NAME).$(SOEXT): $(OBJS) 55 | $(CC) $(LDFLAGS) $(LINKSHARED) $^ $(LDLIBS) -o $@ 56 | ifneq ($(STRIP),) 57 | $(STRIP) $@ 58 | endif 59 | 60 | $(LANGUAGE_NAME).pc: bindings/c/$(LANGUAGE_NAME).pc.in 61 | sed -e 's|@PROJECT_VERSION@|$(VERSION)|' \ 62 | -e 's|@CMAKE_INSTALL_LIBDIR@|$(LIBDIR:$(PREFIX)/%=%)|' \ 63 | -e 's|@CMAKE_INSTALL_INCLUDEDIR@|$(INCLUDEDIR:$(PREFIX)/%=%)|' \ 64 | -e 's|@PROJECT_DESCRIPTION@|$(DESCRIPTION)|' \ 65 | -e 's|@PROJECT_HOMEPAGE_URL@|$(HOMEPAGE_URL)|' \ 66 | -e 's|@CMAKE_INSTALL_PREFIX@|$(PREFIX)|' $< > $@ 67 | 68 | $(PARSER): $(SRC_DIR)/grammar.json 69 | $(TS) generate $^ 70 | 71 | install: all 72 | install -d '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)' 73 | install -m644 bindings/c/$(LANGUAGE_NAME).h '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h 74 | install -m644 $(LANGUAGE_NAME).pc '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc 75 | install -m644 lib$(LANGUAGE_NAME).a '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a 76 | install -m755 lib$(LANGUAGE_NAME).$(SOEXT) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) 77 | ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) 78 | ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT) 79 | 80 | uninstall: 81 | $(RM) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a \ 82 | '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) \ 83 | '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) \ 84 | '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT) \ 85 | '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h \ 86 | '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc 87 | 88 | clean: 89 | $(RM) $(OBJS) $(LANGUAGE_NAME).pc lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) 90 | 91 | test: 92 | $(TS) test 93 | 94 | .PHONY: all install uninstall clean test 95 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "TreeSitterR", 6 | products: [ 7 | .library(name: "TreeSitterR", targets: ["TreeSitterR"]), 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/ChimeHQ/SwiftTreeSitter", from: "0.8.0"), 11 | ], 12 | targets: [ 13 | .target( 14 | name: "TreeSitterR", 15 | dependencies: [], 16 | path: ".", 17 | sources: [ 18 | "src/parser.c", 19 | "src/scanner.c" 20 | ], 21 | resources: [ 22 | .copy("queries") 23 | ], 24 | publicHeadersPath: "bindings/swift", 25 | cSettings: [.headerSearchPath("src")] 26 | ), 27 | .testTarget( 28 | name: "TreeSitterRTests", 29 | dependencies: [ 30 | "SwiftTreeSitter", 31 | "TreeSitterR", 32 | ], 33 | path: "bindings/swift/TreeSitterRTests" 34 | ) 35 | ], 36 | cLanguageStandard: .c11 37 | ) 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tree-sitter-r 2 | 3 | An R grammar for [tree-sitter](https://github.com/tree-sitter/tree-sitter). 4 | 5 | ## R package 6 | 7 | This grammar is available as an [R package](https://cran.r-project.org/web/packages/treesitter.r/index.html). 8 | 9 | You'll also want the [R package providing bindings to tree-sitter](https://davisvaughan.github.io/r-tree-sitter/) itself. 10 | 11 | ## Rust bindings 12 | 13 | This grammar is available as a [Rust crate on crates.io](https://crates.io/crates/tree-sitter-r). 14 | 15 | ## Node bindings 16 | 17 | This grammar is available as an [npm package](https://www.npmjs.com/package/@davisvaughan/tree-sitter-r). 18 | 19 | Note that it is currently listed as a scoped package under the name `@davisvaughan/tree-sitter-r`. 20 | We are working with the npm team to gain ownership of the `tree-sitter-r` package. 21 | Once that happens, we will move the npm package there instead. 22 | 23 | ## References 24 | 25 | - [The R Draft Spec](https://cran.r-project.org/doc/manuals/r-release/R-lang.pdf) 26 | - [gram.y](https://github.com/wch/r-source/blob/trunk/src/main/gram.y) 27 | 28 | ## Known deviations 29 | 30 | This section describes known deviations from the R grammar. 31 | 32 | ### `]]` as a literal token 33 | 34 | The following is valid R syntax, note how `]]` has been split over multiple lines. 35 | 36 | ```r 37 | x[["a"] 38 | ] 39 | ``` 40 | 41 | This applies to `]]`, but not to `[[`, for example, this is not valid R syntax: 42 | 43 | ```r 44 | x[ 45 | ["a"]] 46 | ``` 47 | 48 | The technical reason for this is that [in the grammar](https://github.com/wch/r-source/blob/988774e05497bcf2cfac47bfbec59d551432e3fb/src/main/gram.y#L508) R treats `[[` as a single token, but `]]` is treated as two individual `]` tokens. 49 | Treating `]]` as two individual `]` tokens allows whitespace, newlines, and even comments to appear between the two `]` tokens: 50 | 51 | ```r 52 | x[["a"] # comment 53 | ] 54 | ``` 55 | 56 | While we'd like to precisely support the R grammar, it is also extremely useful to treat all of `(`, `)`, `[`, `]`, `[[`, and `]]` as literal tokens when using the tree-sitter grammar. 57 | This allows you to treat call, subset, and subset2 nodes in the same way, since they all have exactly the same node structure. 58 | 59 | Because treating `]]` as a literal token is so useful, and because we've never seen any R code "in the wild" written this way, this grammar does not allow whitespace, newlines, or comments between the two `]` tokens. 60 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "tree_sitter_r_binding", 5 | "dependencies": [ 6 | " 2 | 3 | typedef struct TSLanguage TSLanguage; 4 | 5 | extern "C" TSLanguage *tree_sitter_r(); 6 | 7 | // "tree-sitter", "language" hashed with BLAKE2 8 | const napi_type_tag LANGUAGE_TYPE_TAG = { 9 | 0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16 10 | }; 11 | 12 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 13 | exports["name"] = Napi::String::New(env, "r"); 14 | auto language = Napi::External::New(env, tree_sitter_r()); 15 | language.TypeTag(&LANGUAGE_TYPE_TAG); 16 | exports["language"] = language; 17 | return exports; 18 | } 19 | 20 | NODE_API_MODULE(tree_sitter_r_binding, Init) 21 | -------------------------------------------------------------------------------- /bindings/node/binding_test.js: -------------------------------------------------------------------------------- 1 | const assert = require("node:assert"); 2 | const { test } = require("node:test"); 3 | 4 | const Parser = require("tree-sitter"); 5 | 6 | test("can load grammar", () => { 7 | const parser = new Parser(); 8 | assert.doesNotThrow(() => parser.setLanguage(require("."))); 9 | }); 10 | -------------------------------------------------------------------------------- /bindings/node/index.d.ts: -------------------------------------------------------------------------------- 1 | type BaseNode = { 2 | type: string; 3 | named: boolean; 4 | }; 5 | 6 | type ChildNode = { 7 | multiple: boolean; 8 | required: boolean; 9 | types: BaseNode[]; 10 | }; 11 | 12 | type NodeInfo = 13 | | (BaseNode & { 14 | subtypes: BaseNode[]; 15 | }) 16 | | (BaseNode & { 17 | fields: { [name: string]: ChildNode }; 18 | children: ChildNode[]; 19 | }); 20 | 21 | type Language = { 22 | name: string; 23 | language: unknown; 24 | nodeTypeInfo: NodeInfo[]; 25 | }; 26 | 27 | declare const language: Language; 28 | export = language; 29 | -------------------------------------------------------------------------------- /bindings/node/index.js: -------------------------------------------------------------------------------- 1 | const root = require("path").join(__dirname, "..", ".."); 2 | 3 | module.exports = 4 | typeof process.versions.bun === "string" 5 | // Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time 6 | ? require(`../../prebuilds/${process.platform}-${process.arch}/tree-sitter-r.node`) 7 | : require("node-gyp-build")(root); 8 | 9 | try { 10 | module.exports.nodeTypeInfo = require("../../src/node-types.json"); 11 | } catch (_) {} 12 | -------------------------------------------------------------------------------- /bindings/python/tests/test_binding.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import tree_sitter, tree_sitter_r 4 | 5 | 6 | class TestLanguage(TestCase): 7 | def test_can_load_grammar(self): 8 | try: 9 | tree_sitter.Language(tree_sitter_r.language()) 10 | except Exception: 11 | self.fail("Error loading R grammar") 12 | -------------------------------------------------------------------------------- /bindings/python/tree_sitter_r/__init__.py: -------------------------------------------------------------------------------- 1 | """R grammar for tree-sitter""" 2 | 3 | from importlib.resources import files as _files 4 | 5 | from ._binding import language 6 | 7 | 8 | def _get_query(name, file): 9 | query = _files(f"{__package__}.queries") / file 10 | globals()[name] = query.read_text() 11 | return globals()[name] 12 | 13 | 14 | def __getattr__(name): 15 | # NOTE: uncomment these to include any queries that this grammar contains: 16 | 17 | if name == "HIGHLIGHTS_QUERY": 18 | return _get_query("HIGHLIGHTS_QUERY", "highlights.scm") 19 | # if name == "INJECTIONS_QUERY": 20 | # return _get_query("INJECTIONS_QUERY", "injections.scm") 21 | if name == "LOCALS_QUERY": 22 | return _get_query("LOCALS_QUERY", "locals.scm") 23 | if name == "TAGS_QUERY": 24 | return _get_query("TAGS_QUERY", "tags.scm") 25 | 26 | raise AttributeError(f"module {__name__!r} has no attribute {name!r}") 27 | 28 | 29 | __all__ = [ 30 | "language", 31 | "HIGHLIGHTS_QUERY", 32 | # "INJECTIONS_QUERY", 33 | "LOCALS_QUERY", 34 | "TAGS_QUERY", 35 | ] 36 | 37 | 38 | def __dir__(): 39 | return sorted(__all__ + [ 40 | "__all__", "__builtins__", "__cached__", "__doc__", "__file__", 41 | "__loader__", "__name__", "__package__", "__path__", "__spec__", 42 | ]) 43 | -------------------------------------------------------------------------------- /bindings/python/tree_sitter_r/__init__.pyi: -------------------------------------------------------------------------------- 1 | from typing import Final 2 | 3 | # NOTE: uncomment these to include any queries that this grammar contains: 4 | 5 | HIGHLIGHTS_QUERY: Final[str] 6 | # INJECTIONS_QUERY: Final[str] 7 | LOCALS_QUERY: Final[str] 8 | TAGS_QUERY: Final[str] 9 | 10 | def language() -> object: ... 11 | -------------------------------------------------------------------------------- /bindings/python/tree_sitter_r/binding.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef struct TSLanguage TSLanguage; 4 | 5 | TSLanguage *tree_sitter_r(void); 6 | 7 | static PyObject* _binding_language(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) { 8 | return PyCapsule_New(tree_sitter_r(), "tree_sitter.Language", NULL); 9 | } 10 | 11 | static PyMethodDef methods[] = { 12 | {"language", _binding_language, METH_NOARGS, 13 | "Get the tree-sitter language for this grammar."}, 14 | {NULL, NULL, 0, NULL} 15 | }; 16 | 17 | static struct PyModuleDef module = { 18 | .m_base = PyModuleDef_HEAD_INIT, 19 | .m_name = "_binding", 20 | .m_doc = NULL, 21 | .m_size = -1, 22 | .m_methods = methods 23 | }; 24 | 25 | PyMODINIT_FUNC PyInit__binding(void) { 26 | return PyModule_Create(&module); 27 | } 28 | -------------------------------------------------------------------------------- /bindings/python/tree_sitter_r/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/tree-sitter-r/95aff097aa927a66bb357f715b58cde821be8867/bindings/python/tree_sitter_r/py.typed -------------------------------------------------------------------------------- /bindings/r/.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^LICENSE\.md$ 2 | ^.cache$ 3 | ^.clang-format$ 4 | ^compile_commands.json$ 5 | ^.vscode$ 6 | ^bootstrap.R$ 7 | ^codecov\.yml$ 8 | ^README\.Rmd$ 9 | ^tools$ 10 | ^.github$ 11 | ^_pkgdown\.yml$ 12 | ^docs$ 13 | ^pkgdown$ 14 | ^cran-comments\.md$ 15 | ^[\.]?air\.toml$ 16 | ^\.vscode$ 17 | -------------------------------------------------------------------------------- /bindings/r/.gitignore: -------------------------------------------------------------------------------- 1 | compile_commands.json 2 | .cache/ 3 | src/tree_sitter/ 4 | src/parser.c 5 | src/scanner.c 6 | docs 7 | -------------------------------------------------------------------------------- /bindings/r/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Posit.air-vscode" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /bindings/r/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[c]": { 3 | "editor.formatOnSave": true, 4 | "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd" 5 | }, 6 | "[r]": { 7 | "editor.formatOnSave": true, 8 | "editor.defaultFormatter": "Posit.air-vscode" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /bindings/r/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: treesitter.r 2 | Title: 'R' Grammar for 'Tree-Sitter' 3 | Version: 1.2.0.9000 4 | Authors@R: c( 5 | person("Davis", "Vaughan", , "davis@posit.co", role = c("aut", "cre")), 6 | person("Posit Software, PBC", role = c("cph", "fnd")), 7 | person("Tree-sitter authors", role = "cph", 8 | comment = "Tree-sitter C headers and parser.c") 9 | ) 10 | Description: Provides bindings to an 'R' grammar for 'Tree-sitter', to be 11 | used alongside the 'treesitter' package. 'Tree-sitter' builds concrete 12 | syntax trees for source files of any language, and can efficiently 13 | update those syntax trees as the source file is edited. 14 | License: MIT + file LICENSE 15 | URL: https://github.com/r-lib/tree-sitter-r 16 | BugReports: https://github.com/r-lib/tree-sitter-r/issues 17 | Depends: 18 | R (>= 4.3.0) 19 | Suggests: 20 | testthat (>= 3.0.0), 21 | treesitter 22 | Config/build/bootstrap: TRUE 23 | Config/Needs/website: tidyverse/tidytemplate 24 | Config/testthat/edition: 3 25 | Encoding: UTF-8 26 | Roxygen: list(markdown = TRUE) 27 | RoxygenNote: 7.3.2 28 | -------------------------------------------------------------------------------- /bindings/r/LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2024 2 | COPYRIGHT HOLDER: treesitter.r authors 3 | -------------------------------------------------------------------------------- /bindings/r/LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2024 treesitter.r authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bindings/r/LICENSE.note: -------------------------------------------------------------------------------- 1 | Tree-sitter C headers and parser.c 2 | -------------------------------------------------------------------------------- 3 | 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2018-2024 Max Brunsfeld 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /bindings/r/NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(language) 4 | useDynLib(treesitter.r, .registration = TRUE) 5 | -------------------------------------------------------------------------------- /bindings/r/NEWS.md: -------------------------------------------------------------------------------- 1 | # treesitter.r (development version) 2 | 3 | # treesitter.r 1.2.0 4 | 5 | * Updated to [tree-sitter-r v1.2.0](https://github.com/r-lib/tree-sitter-r/blob/main/CHANGELOG.md) (#136). 6 | 7 | # treesitter.r 1.1.0 8 | 9 | * Updated to [tree-sitter-r v1.1.0](https://github.com/r-lib/tree-sitter-r/blob/main/CHANGELOG.md) (#136). 10 | 11 | # treesitter.r 1.0.1 12 | 13 | * Initial CRAN submission. 14 | -------------------------------------------------------------------------------- /bindings/r/R/abi.R: -------------------------------------------------------------------------------- 1 | # This file is generated by `tools/abi.R`, do not modify by hand. 2 | 3 | #' tree-sitter ABI version 4 | #' 5 | #' `abi()` returns the ABI version that `parser.c` was generated with. This is 6 | #' used to verify that the grammar is compatible with the tree-sitter C library 7 | #' embedded within the treesitter package. 8 | #' 9 | #' @returns A single integer. 10 | #' 11 | #' @noRd 12 | abi <- function() { 13 | # ABI: 14 14 | 14L 15 | } 16 | -------------------------------------------------------------------------------- /bindings/r/R/import-standalone-language.R: -------------------------------------------------------------------------------- 1 | # Standalone file: do not edit by hand 2 | # Source: 3 | # ---------------------------------------------------------------------- 4 | # 5 | # --- 6 | # repo: DavisVaughan/r-tree-sitter 7 | # file: standalone-language.R 8 | # last-updated: 2024-06-17 9 | # license: https://unlicense.org 10 | # --- 11 | # 12 | # ## Changelog 13 | # 14 | # 2024-06-17: 15 | # - Added `abi` argument. 16 | # 17 | # 2024-04-11: 18 | # - Initial implementation. 19 | # 20 | # nocov start 21 | 22 | #' Construct a new tree-sitter language object 23 | #' 24 | #' @description 25 | #' This function is a developer tool to wrap an external pointer to a 26 | #' C level tree-sitter `const TSLanguage*`. It is not exported, but should 27 | #' be copied into grammar specific R packages and called by them, providing 28 | #' the language name and an external pointer to the result of their C level 29 | #' `tree_sitter_{name}()` function. 30 | #' 31 | #' @param pointer `[external_pointer]` 32 | #' 33 | #' An external pointer to a `const TSLanguage*`. 34 | #' 35 | #' @param abi `[integer]` 36 | #' 37 | #' The ABI version of tree-sitter that `parser.c` was generated with. 38 | #' 39 | #' @param name `[string]` 40 | #' 41 | #' The name of the language being wrapped. 42 | #' 43 | #' This will eventually be removed when tree-sitter supports extracting the 44 | #' language name from the `pointer`. 45 | #' 46 | #' @param ... Not used. 47 | #' 48 | #' @returns 49 | #' A `tree_sitter_language` object. 50 | #' 51 | #' @noRd 52 | new_language <- function(pointer, ..., abi = NULL, name = NULL) { 53 | if (typeof(pointer) != "externalptr") { 54 | stop("`pointer` must be an external pointer.") 55 | } 56 | 57 | # TODO: Remove `abi` if we ever figure out a better way to check compatibility 58 | # with treesitter (probably not likely). 59 | if (is.null(abi)) { 60 | stop("`abi` currently must be supplied.") 61 | } 62 | if (!is.integer(abi) || length(abi) != 1L || is.na(abi)) { 63 | stop("`abi` must be a single integer.") 64 | } 65 | 66 | # TODO: Remove `name` argument if name is accessible in language object 67 | # https://github.com/tree-sitter/tree-sitter/pull/3184 68 | if (is.null(name)) { 69 | stop("`name` currently must be supplied.") 70 | } 71 | if (!is.character(name) || length(name) != 1L || is.na(name)) { 72 | stop("`name` must be a string.") 73 | } 74 | 75 | out <- list(pointer = pointer, abi = abi, name = name) 76 | class(out) <- "tree_sitter_language" 77 | 78 | out 79 | } 80 | 81 | # nocov end 82 | -------------------------------------------------------------------------------- /bindings/r/R/language.R: -------------------------------------------------------------------------------- 1 | #' tree-sitter language for R 2 | #' 3 | #' `language()` returns a `tree_sitter_language` object for R for use with the 4 | #' treesitter package. 5 | #' 6 | #' @returns A `tree_sitter_language` object. 7 | #' 8 | #' @export 9 | #' @examples 10 | #' language() 11 | language <- function() { 12 | pointer <- .Call(ffi_language) 13 | 14 | new_language( 15 | pointer = pointer, 16 | abi = abi(), 17 | name = "r" 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /bindings/r/R/treesitter.r-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | "_PACKAGE" 3 | 4 | ## usethis namespace: start 5 | #' @useDynLib treesitter.r, .registration = TRUE 6 | ## usethis namespace: end 7 | NULL 8 | -------------------------------------------------------------------------------- /bindings/r/R/zzz.R: -------------------------------------------------------------------------------- 1 | # nocov start 2 | 3 | .onLoad <- function(libname, pkgname) { 4 | if (Sys.getenv("DEVTOOLS_LOAD") == "treesitter.r") { 5 | # Support syncing with `load_all()` 6 | path <- file.path(".", "bootstrap.R") 7 | path <- normalizePath(path, mustWork = TRUE) 8 | source(path) 9 | } 10 | } 11 | 12 | # nocov end 13 | -------------------------------------------------------------------------------- /bindings/r/README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | 16 | # treesitter.r 17 | 18 | 19 | [![R-CMD-check](https://github.com/r-lib/tree-sitter-r/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/tree-sitter-r/actions/workflows/R-CMD-check.yaml) 20 | 21 | 22 | treesitter.r exposes the R tree-sitter grammar for use with the language agnostic [treesitter](https://github.com/DavisVaughan/r-tree-sitter) package. To use it, provide `treesitter.r::language()` as an argument to `treesitter::parser()`. 23 | 24 | ## Installation 25 | 26 | Install treesitter.r from CRAN with: 27 | 28 | ``` r 29 | install.packages("treesitter.r") 30 | ``` 31 | 32 | You can install the development version of treesitter.r from [GitHub](https://github.com/) with: 33 | 34 | ``` r 35 | # install.packages("pak") 36 | pak::pak("r-lib/tree-sitter-r/bindings/r") 37 | ``` 38 | -------------------------------------------------------------------------------- /bindings/r/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # treesitter.r 5 | 6 | 7 | 8 | [![R-CMD-check](https://github.com/r-lib/tree-sitter-r/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/tree-sitter-r/actions/workflows/R-CMD-check.yaml) 9 | 10 | 11 | treesitter.r exposes the R tree-sitter grammar for use with the language 12 | agnostic [treesitter](https://github.com/DavisVaughan/r-tree-sitter) 13 | package. To use it, provide `treesitter.r::language()` as an argument to 14 | `treesitter::parser()`. 15 | 16 | ## Installation 17 | 18 | Install treesitter.r from CRAN with: 19 | 20 | ``` r 21 | install.packages("treesitter.r") 22 | ``` 23 | 24 | You can install the development version of treesitter.r from 25 | [GitHub](https://github.com/) with: 26 | 27 | ``` r 28 | # install.packages("pak") 29 | pak::pak("r-lib/tree-sitter-r/bindings/r") 30 | ``` 31 | -------------------------------------------------------------------------------- /bindings/r/_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: ~ 2 | 3 | development: 4 | mode: auto 5 | 6 | template: 7 | package: tidytemplate 8 | bootstrap: 5 9 | -------------------------------------------------------------------------------- /bindings/r/air.toml: -------------------------------------------------------------------------------- 1 | [format] 2 | exclude = ["tests/testthat/references"] 3 | -------------------------------------------------------------------------------- /bindings/r/bootstrap.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | # Update this list if more tree-sitter files are required 4 | files <- c( 5 | file.path("tree_sitter", "alloc.h"), 6 | file.path("tree_sitter", "array.h"), 7 | file.path("tree_sitter", "parser.h"), 8 | "parser.c", 9 | "scanner.c" 10 | ) 11 | 12 | upstream_directory <- file.path("..", "..", "src") 13 | 14 | upstream <- file.path(upstream_directory, files) 15 | destination <- file.path("src", files) 16 | 17 | # Assumes that you have forked this repo in its entirety, but your 18 | # current working directory is `bindings/r/`. 19 | sync <- function(upstream_directory, upstream, destination) { 20 | upstream_directory <- normalizePath(upstream_directory, mustWork = FALSE) 21 | 22 | if (dir.exists(upstream_directory)) { 23 | # Typical case in CI checks, `devtools::install_github()`, `pak::pak()` 24 | # where the full git repo directory is in place 25 | sync_with_upstream(upstream, destination) 26 | } else { 27 | # `pak::pak("local::.")`, and in particular pkgdown CI which uses 28 | # `local::.`, where pkgdepends copies only the package directory to a 29 | # temp directory. In this case, `bootstrap.R` must have already been run. 30 | sync_without_upstream(destination) 31 | } 32 | 33 | invisible() 34 | } 35 | 36 | sync_with_upstream <- function(upstream, destination) { 37 | any_updated <- FALSE 38 | 39 | for (i in seq_along(upstream)) { 40 | updated <- sync_with_upstream_one(upstream[[i]], destination[[i]]) 41 | any_updated <- any_updated || updated 42 | } 43 | 44 | if (any_updated) { 45 | message( 46 | "If using `load_all()`, call it again to recompile with updated source files." 47 | ) 48 | } else { 49 | message("All parent tree-sitter files were up to date.") 50 | } 51 | } 52 | 53 | sync_with_upstream_one <- function(upstream, destination) { 54 | shortname <- destination 55 | is_parser_c <- identical(shortname, file.path("src", "parser.c")) 56 | 57 | upstream <- normalizePath(upstream, mustWork = TRUE) 58 | destination <- normalizePath(destination, mustWork = FALSE) 59 | 60 | dir_destination <- dirname(destination) 61 | if (!dir.exists(dir_destination)) { 62 | message(sprintf("Creating `%s` directory.", dir_destination)) 63 | dir.create(dir_destination, recursive = TRUE) 64 | } 65 | 66 | update <- needs_update(upstream, destination) 67 | 68 | if (update) { 69 | message(sprintf("`%s` is out of date, updating.", shortname)) 70 | file.copy(upstream, destination, overwrite = TRUE) 71 | patch_pragmas(destination) 72 | } 73 | 74 | if (is_parser_c) { 75 | # In the case of `parser.c`, we want to sync up the ABI version with `abi.R` when 76 | # developing interactively, but in CI checks and with `pak::pak()` or 77 | # `devtools::install_github()`, we just want to check that the ABIs are aligned. 78 | if (in_load_all()) { 79 | update_abi() 80 | } else { 81 | check_abi() 82 | } 83 | } 84 | 85 | update 86 | } 87 | 88 | sync_without_upstream <- function(destination) { 89 | message(paste0( 90 | "Can't find parent tree-sitter directory, ", 91 | "R package has likely been moved to a temporary directory. ", 92 | "Checking if required files already exist from a previous `bootstrap.R` run." 93 | )) 94 | 95 | if (!all(file.exists(destination))) { 96 | stop(paste0( 97 | "Can't find required tree-sitter files, ", 98 | "and can't find the parent tree-sitter directory to copy from. ", 99 | "Do you need to run `bootstrap.R` before the package ", 100 | "is moved to the temporary directory?" 101 | )) 102 | } 103 | 104 | # When we don't have the parent directory, just check that the preexisting 105 | # `R/abi.R` and `src/parser.c` ABIs are in sync. 106 | check_abi() 107 | 108 | message(paste0( 109 | "Found required files, ", 110 | "proceeding with no guarantees that they are up to date!" 111 | )) 112 | 113 | invisible() 114 | } 115 | 116 | needs_update <- function(upstream, destination) { 117 | if (!file.exists(destination)) { 118 | # First time ever 119 | return(TRUE) 120 | } 121 | 122 | upstream_modified <- file.info(upstream)$mtime 123 | destination_modified <- file.info(destination)$mtime 124 | 125 | isTRUE(upstream_modified > destination_modified) 126 | } 127 | 128 | # For CRAN 129 | patch_pragmas <- function(path) { 130 | lines <- readLines(path) 131 | lines <- gsub("#pragma", "# pragma", lines) 132 | writeLines(lines, path) 133 | } 134 | 135 | parser_c_path <- function() { 136 | path <- file.path(".", "src", "parser.c") 137 | normalizePath(path, mustWork = TRUE) 138 | } 139 | 140 | abi_r_path <- function() { 141 | path <- file.path(".", "R", "abi.R") 142 | normalizePath(path, mustWork = FALSE) 143 | } 144 | 145 | check_abi <- function() { 146 | path <- parser_c_path() 147 | version <- get_c_abi(path) 148 | 149 | path <- abi_r_path() 150 | 151 | if (!file.exists(path)) { 152 | stop( 153 | "Can't find `R/abi.R` file. Do you need to run `bootstrap.R` locally first?" 154 | ) 155 | } 156 | 157 | r_version <- get_r_abi(path) 158 | 159 | if (identical(version, r_version)) { 160 | # Synced up 161 | return(invisible(NULL)) 162 | } 163 | 164 | stop(paste( 165 | sep = "\n", 166 | "`R/abi.R` and `src/parser.c` are out of sync. ", 167 | sprintf("`R/abi.R` reports the ABI version as %s.", r_version), 168 | sprintf("`src/parser.c` reports the ABI version as %s.", version) 169 | )) 170 | } 171 | 172 | update_abi <- function() { 173 | path <- parser_c_path() 174 | version <- get_c_abi(path) 175 | 176 | path <- abi_r_path() 177 | 178 | if (file.exists(path)) { 179 | r_version <- get_r_abi(path) 180 | 181 | if (identical(version, r_version)) { 182 | # Already synced up 183 | return(invisible(NULL)) 184 | } 185 | 186 | # ABI has updated 187 | message(sprintf( 188 | "Existing ABI version is %s, new ABI version is %s, updating.", 189 | r_version, 190 | version 191 | )) 192 | } else { 193 | message(sprintf( 194 | "`abi.R` doesn't exist yet, creating it now with ABI version %s. Make sure to run `devtools::document()`.", 195 | version 196 | )) 197 | } 198 | 199 | write_abi(version) 200 | } 201 | 202 | write_abi <- function(version) { 203 | path <- file.path("tools", "abi.R") 204 | path <- normalizePath(path, mustWork = TRUE) 205 | 206 | destination <- file.path("R", "abi.R") 207 | destination <- normalizePath(destination, mustWork = FALSE) 208 | 209 | lines <- readLines(path) 210 | lines <- gsub("LANGUAGE_VERSION", version, lines, fixed = TRUE) 211 | writeLines(lines, destination) 212 | 213 | invisible(NULL) 214 | } 215 | 216 | get_r_abi <- function(path) { 217 | lines <- readLines(path) 218 | 219 | pattern <- "ABI: (\\d+)" 220 | line <- grep(pattern, lines, value = TRUE) 221 | 222 | if (length(line) != 1L) { 223 | stop("Can't find `ABI:` line in `abi.R`.") 224 | } 225 | 226 | version <- get_one_capture(line, pattern) 227 | version <- as.integer(version) 228 | 229 | if (is.na(version)) { 230 | stop("Can't parse ABI version from `abi.R`.") 231 | } 232 | 233 | version 234 | } 235 | 236 | get_c_abi <- function(path) { 237 | # It's a big file! 238 | lines <- readLines(path, n = 50L) 239 | 240 | pattern <- "^#define LANGUAGE_VERSION (\\d+)$" 241 | line <- grep(pattern, lines, value = TRUE) 242 | 243 | if (length(line) != 1L) { 244 | stop("Can't find `LANGUAGE_VERSION` line in `parser.c`.") 245 | } 246 | 247 | version <- get_one_capture(line, pattern) 248 | version <- as.integer(version) 249 | 250 | if (is.na(version)) { 251 | stop("Can't parse `LANGUAGE_VERSION` from `parser.c`.") 252 | } 253 | 254 | version 255 | } 256 | 257 | get_one_capture <- function(line, pattern) { 258 | # Find positions of matches 259 | positions <- regexec(pattern, line) 260 | 261 | # Extract out match content. We only have 1 line, so `[[1L]]`. 262 | match <- regmatches(line, positions)[[1L]] 263 | 264 | # Matches are in the form of: 265 | # - Element 1 is the whole match 266 | # - Element 2 is the first capture group, which is what we care about 267 | capture <- match[[2L]] 268 | 269 | if (length(capture) != 1L || !is.character(capture)) { 270 | stop("Failed to extract exactly one capture group.") 271 | } 272 | 273 | capture 274 | } 275 | 276 | in_load_all <- function() { 277 | Sys.getenv("DEVTOOLS_LOAD") == "treesitter.r" 278 | } 279 | 280 | # Run it! 281 | sync(upstream_directory, upstream, destination) 282 | -------------------------------------------------------------------------------- /bindings/r/codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /bindings/r/cran-comments.md: -------------------------------------------------------------------------------- 1 | This release is not expected to break any reverse dependencies. 2 | -------------------------------------------------------------------------------- /bindings/r/man/language.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/language.R 3 | \name{language} 4 | \alias{language} 5 | \title{tree-sitter language for R} 6 | \usage{ 7 | language() 8 | } 9 | \value{ 10 | A \code{tree_sitter_language} object. 11 | } 12 | \description{ 13 | \code{language()} returns a \code{tree_sitter_language} object for R for use with the 14 | treesitter package. 15 | } 16 | \examples{ 17 | language() 18 | } 19 | -------------------------------------------------------------------------------- /bindings/r/man/treesitter.r-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/treesitter.r-package.R 3 | \docType{package} 4 | \name{treesitter.r-package} 5 | \alias{treesitter.r} 6 | \alias{treesitter.r-package} 7 | \title{treesitter.r: 'R' Grammar for 'Tree-Sitter'} 8 | \description{ 9 | Provides bindings to an 'R' grammar for 'Tree-sitter', to be used alongside the 'treesitter' package. 'Tree-sitter' builds concrete syntax trees for source files of any language, and can efficiently update those syntax trees as the source file is edited. 10 | } 11 | \seealso{ 12 | Useful links: 13 | \itemize{ 14 | \item \url{https://github.com/r-lib/tree-sitter-r} 15 | \item Report bugs at \url{https://github.com/r-lib/tree-sitter-r/issues} 16 | } 17 | 18 | } 19 | \author{ 20 | \strong{Maintainer}: Davis Vaughan \email{davis@posit.co} 21 | 22 | Other contributors: 23 | \itemize{ 24 | \item Posit Software, PBC [copyright holder, funder] 25 | \item Tree-sitter authors (Tree-sitter C headers and parser.c) [copyright holder] 26 | } 27 | 28 | } 29 | \keyword{internal} 30 | -------------------------------------------------------------------------------- /bindings/r/src/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.dll 4 | -------------------------------------------------------------------------------- /bindings/r/src/binding.c: -------------------------------------------------------------------------------- 1 | #define R_NO_REMAP 2 | #include 3 | #include 4 | 5 | #include "tree_sitter/parser.h" 6 | 7 | // `parser.c` 8 | extern const TSLanguage* tree_sitter_r(void); 9 | 10 | SEXP ffi_language(void) { 11 | static SEXP language = NULL; 12 | 13 | if (language == NULL) { 14 | const TSLanguage* pointer = tree_sitter_r(); 15 | language = R_MakeExternalPtr((void*) pointer, R_NilValue, R_NilValue); 16 | R_PreserveObject(language); 17 | } 18 | 19 | return language; 20 | } 21 | -------------------------------------------------------------------------------- /bindings/r/src/init.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include // for NULL 5 | 6 | extern SEXP ffi_language(void); 7 | 8 | static const R_CallMethodDef CallEntries[] = { 9 | {"ffi_language", (DL_FUNC) &ffi_language, 0}, 10 | {NULL, NULL, 0}}; 11 | 12 | void R_init_treesitter_r(DllInfo* dll) { 13 | R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); 14 | R_useDynamicSymbols(dll, FALSE); 15 | } 16 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview 7 | # * https://testthat.r-lib.org/articles/special-files.html 8 | 9 | library(testthat) 10 | library(treesitter.r) 11 | 12 | test_check("treesitter.r") 13 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/_snaps/delimiter.md: -------------------------------------------------------------------------------- 1 | # closing brace 2 | 3 | Code 4 | node_children_print(node) 5 | Output 6 | S-Expression 7 | (ERROR [(1, 0), (1, 1)] 8 | (ERROR [(1, 0), (1, 1)]) 9 | ) 10 | 11 | Text 12 | } 13 | 14 | 15 | # closing parenthesis 16 | 17 | Code 18 | node_children_print(node) 19 | Output 20 | S-Expression 21 | (ERROR [(1, 0), (1, 1)] 22 | (ERROR [(1, 0), (1, 1)]) 23 | ) 24 | 25 | Text 26 | ) 27 | 28 | 29 | # closing bracket 30 | 31 | Code 32 | node_children_print(node) 33 | Output 34 | S-Expression 35 | (ERROR [(1, 0), (1, 1)] 36 | (ERROR [(1, 0), (1, 1)]) 37 | ) 38 | 39 | Text 40 | ] 41 | 42 | 43 | # opening brace, closing parenthesis 44 | 45 | Code 46 | node_children_print(node) 47 | Output 48 | S-Expression 49 | (comment [(1, 0), (1, 72)]) 50 | 51 | Text 52 | # Parenthesis is "not valid" so it isn't matched by the external scanner 53 | 54 | S-Expression 55 | (ERROR [(2, 0), (2, 2)] 56 | "{" [(2, 0), (2, 1)] 57 | (ERROR [(2, 1), (2, 2)]) 58 | ) 59 | 60 | Text 61 | {) 62 | 63 | 64 | # opening parenthesis, closing brace 65 | 66 | Code 67 | node_children_print(node) 68 | Output 69 | S-Expression 70 | (ERROR [(1, 0), (1, 2)] 71 | "(" [(1, 0), (1, 1)] 72 | (ERROR [(1, 1), (1, 2)]) 73 | ) 74 | 75 | Text 76 | (} 77 | 78 | 79 | # opening parenthesis, closing bracket 80 | 81 | Code 82 | node_children_print(node) 83 | Output 84 | S-Expression 85 | (ERROR [(1, 0), (1, 2)] 86 | "(" [(1, 0), (1, 1)] 87 | (ERROR [(1, 1), (1, 2)]) 88 | ) 89 | 90 | Text 91 | (] 92 | 93 | 94 | # opening bracket2, unmatched closing bracket 95 | 96 | Code 97 | node_children_print(node) 98 | Output 99 | S-Expression 100 | (identifier [(1, 0), (1, 1)]) 101 | 102 | Text 103 | x 104 | 105 | S-Expression 106 | (ERROR [(1, 1), (1, 5)] 107 | "[[" [(1, 1), (1, 3)] 108 | (ERROR [(1, 4), (1, 5)]) 109 | ) 110 | 111 | Text 112 | [[2] 113 | 114 | 115 | # opening bracket and bracket2, unmatched closing bracket 116 | 117 | Code 118 | node_children_print(node) 119 | Output 120 | S-Expression 121 | (identifier [(1, 0), (1, 1)]) 122 | 123 | Text 124 | x 125 | 126 | S-Expression 127 | (ERROR [(1, 1), (1, 7)] 128 | "[" [(1, 1), (1, 2)] 129 | (identifier [(1, 2), (1, 3)]) 130 | "[[" [(1, 3), (1, 5)] 131 | (ERROR [(1, 6), (1, 7)]) 132 | ) 133 | 134 | Text 135 | [y[[2] 136 | 137 | 138 | # opening bracket2 and bracket, matched closing bracket 139 | 140 | Code 141 | node_children_print(node) 142 | Output 143 | S-Expression 144 | (subset2 [(1, 0), (1, 7)] 145 | function: (identifier [(1, 0), (1, 1)]) 146 | arguments: (arguments [(1, 1), (1, 7)] 147 | open: "[[" [(1, 1), (1, 3)] 148 | argument: (argument [(1, 3), (1, 7)] 149 | value: (subset [(1, 3), (1, 7)] 150 | function: (identifier [(1, 3), (1, 4)]) 151 | arguments: (arguments [(1, 4), (1, 7)] 152 | open: "[" [(1, 4), (1, 5)] 153 | argument: (argument [(1, 5), (1, 6)] 154 | value: (float [(1, 5), (1, 6)]) 155 | ) 156 | close: "]" [(1, 6), (1, 7)] 157 | ) 158 | ) 159 | ) 160 | close: "]]" MISSING [(1, 7), (1, 7)] 161 | ) 162 | ) 163 | 164 | Text 165 | x[[y[2] 166 | 167 | 168 | # opening bracket2 and bracket, unmatched closing bracket 169 | 170 | Code 171 | node_children_print(node) 172 | Output 173 | S-Expression 174 | (identifier [(1, 0), (1, 1)]) 175 | 176 | Text 177 | x 178 | 179 | S-Expression 180 | (ERROR [(1, 1), (1, 8)] 181 | "[[" [(1, 1), (1, 3)] 182 | (subset [(1, 3), (1, 7)] 183 | function: (identifier [(1, 3), (1, 4)]) 184 | arguments: (arguments [(1, 4), (1, 7)] 185 | open: "[" [(1, 4), (1, 5)] 186 | argument: (argument [(1, 5), (1, 6)] 187 | value: (float [(1, 5), (1, 6)]) 188 | ) 189 | close: "]" [(1, 6), (1, 7)] 190 | ) 191 | ) 192 | (ERROR [(1, 7), (1, 8)]) 193 | ) 194 | 195 | Text 196 | [[y[2]] 197 | 198 | 199 | # for no closing `)` 200 | 201 | Code 202 | node_children_print(node) 203 | Output 204 | S-Expression 205 | (ERROR [(1, 0), (3, 7)] 206 | "for" [(1, 0), (1, 3)] 207 | "(" [(1, 4), (1, 5)] 208 | (identifier [(1, 5), (1, 6)]) 209 | "in" [(1, 7), (1, 9)] 210 | (binary_operator [(1, 10), (3, 7)] 211 | lhs: (identifier [(1, 10), (1, 13)]) 212 | (ERROR [(3, 2), (3, 3)]) 213 | operator: "+" [(3, 4), (3, 5)] 214 | rhs: (float [(3, 6), (3, 7)]) 215 | ) 216 | ) 217 | 218 | Text 219 | for (i in vec 220 | 221 | 1 + 1 222 | 223 | 224 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/_snaps/extract.md: -------------------------------------------------------------------------------- 1 | # dollar 2 | 3 | Code 4 | node_children_print(node) 5 | Output 6 | S-Expression 7 | (extract_operator [(1, 0), (1, 7)] 8 | lhs: (identifier [(1, 0), (1, 3)]) 9 | operator: "$" [(1, 3), (1, 4)] 10 | rhs: (identifier [(1, 4), (1, 7)]) 11 | ) 12 | 13 | Text 14 | foo$bar 15 | 16 | S-Expression 17 | (extract_operator [(2, 0), (2, 11)] 18 | lhs: (extract_operator [(2, 0), (2, 7)] 19 | lhs: (identifier [(2, 0), (2, 3)]) 20 | operator: "$" [(2, 3), (2, 4)] 21 | rhs: (identifier [(2, 4), (2, 7)]) 22 | ) 23 | operator: "$" [(2, 7), (2, 8)] 24 | rhs: (identifier [(2, 8), (2, 11)]) 25 | ) 26 | 27 | Text 28 | foo$bar$baz 29 | 30 | S-Expression 31 | (extract_operator [(3, 0), (3, 11)] 32 | lhs: (extract_operator [(3, 0), (3, 7)] 33 | lhs: (identifier [(3, 0), (3, 3)]) 34 | operator: "$" [(3, 3), (3, 4)] 35 | rhs: (identifier [(3, 4), (3, 7)]) 36 | ) 37 | operator: "@" [(3, 7), (3, 8)] 38 | rhs: (identifier [(3, 8), (3, 11)]) 39 | ) 40 | 41 | Text 42 | foo$bar@baz 43 | 44 | S-Expression 45 | (call [(4, 0), (4, 9)] 46 | function: (extract_operator [(4, 0), (4, 7)] 47 | lhs: (identifier [(4, 0), (4, 3)]) 48 | operator: "$" [(4, 3), (4, 4)] 49 | rhs: (identifier [(4, 4), (4, 7)]) 50 | ) 51 | arguments: (arguments [(4, 7), (4, 9)] 52 | open: "(" [(4, 7), (4, 8)] 53 | close: ")" [(4, 8), (4, 9)] 54 | ) 55 | ) 56 | 57 | Text 58 | foo$bar() 59 | 60 | S-Expression 61 | (extract_operator [(5, 0), (5, 9)] 62 | lhs: (identifier [(5, 0), (5, 3)]) 63 | operator: "$" [(5, 3), (5, 4)] 64 | rhs: (string [(5, 4), (5, 9)] 65 | open: "\"" [(5, 4), (5, 5)] 66 | content: (string_content [(5, 5), (5, 8)]) 67 | close: "\"" [(5, 8), (5, 9)] 68 | ) 69 | ) 70 | 71 | Text 72 | foo$"bar" 73 | 74 | S-Expression 75 | (extract_operator [(6, 0), (6, 22)] 76 | lhs: (subset2 [(6, 0), (6, 18)] 77 | function: (extract_operator [(6, 0), (6, 13)] 78 | lhs: (call [(6, 0), (6, 9)] 79 | function: (extract_operator [(6, 0), (6, 7)] 80 | lhs: (identifier [(6, 0), (6, 3)]) 81 | operator: "$" [(6, 3), (6, 4)] 82 | rhs: (identifier [(6, 4), (6, 7)]) 83 | ) 84 | arguments: (arguments [(6, 7), (6, 9)] 85 | open: "(" [(6, 7), (6, 8)] 86 | close: ")" [(6, 8), (6, 9)] 87 | ) 88 | ) 89 | operator: "$" [(6, 9), (6, 10)] 90 | rhs: (identifier [(6, 10), (6, 13)]) 91 | ) 92 | arguments: (arguments [(6, 13), (6, 18)] 93 | open: "[[" [(6, 13), (6, 15)] 94 | argument: (argument [(6, 15), (6, 16)] 95 | value: (float [(6, 15), (6, 16)]) 96 | ) 97 | close: "]]" [(6, 16), (6, 18)] 98 | ) 99 | ) 100 | operator: "$" [(6, 18), (6, 19)] 101 | rhs: (identifier [(6, 19), (6, 22)]) 102 | ) 103 | 104 | Text 105 | foo$bar()$baz[[1]]$bam 106 | 107 | S-Expression 108 | (extract_operator [(7, 0), (7, 7)] 109 | lhs: (identifier [(7, 0), (7, 3)]) 110 | operator: "$" [(7, 3), (7, 4)] 111 | rhs: (dots [(7, 4), (7, 7)]) 112 | ) 113 | 114 | Text 115 | foo$... 116 | 117 | S-Expression 118 | (extract_operator [(8, 0), (8, 7)] 119 | lhs: (identifier [(8, 0), (8, 3)]) 120 | operator: "$" [(8, 3), (8, 4)] 121 | rhs: (dot_dot_i [(8, 4), (8, 7)]) 122 | ) 123 | 124 | Text 125 | foo$..1 126 | 127 | 128 | # dollar no rhs 129 | 130 | Code 131 | node_children_print(node) 132 | Output 133 | S-Expression 134 | (extract_operator [(1, 0), (2, 0)] 135 | lhs: (identifier [(1, 0), (1, 3)]) 136 | operator: "$" [(1, 3), (1, 4)] 137 | ) 138 | 139 | Text 140 | foo$ 141 | 142 | 143 | 144 | # slot 145 | 146 | Code 147 | node_children_print(node) 148 | Output 149 | S-Expression 150 | (extract_operator [(1, 0), (1, 7)] 151 | lhs: (identifier [(1, 0), (1, 3)]) 152 | operator: "@" [(1, 3), (1, 4)] 153 | rhs: (identifier [(1, 4), (1, 7)]) 154 | ) 155 | 156 | Text 157 | foo@bar 158 | 159 | S-Expression 160 | (extract_operator [(2, 0), (2, 11)] 161 | lhs: (extract_operator [(2, 0), (2, 7)] 162 | lhs: (identifier [(2, 0), (2, 3)]) 163 | operator: "@" [(2, 3), (2, 4)] 164 | rhs: (identifier [(2, 4), (2, 7)]) 165 | ) 166 | operator: "$" [(2, 7), (2, 8)] 167 | rhs: (identifier [(2, 8), (2, 11)]) 168 | ) 169 | 170 | Text 171 | foo@bar$baz 172 | 173 | S-Expression 174 | (call [(3, 0), (3, 9)] 175 | function: (extract_operator [(3, 0), (3, 7)] 176 | lhs: (identifier [(3, 0), (3, 3)]) 177 | operator: "@" [(3, 3), (3, 4)] 178 | rhs: (identifier [(3, 4), (3, 7)]) 179 | ) 180 | arguments: (arguments [(3, 7), (3, 9)] 181 | open: "(" [(3, 7), (3, 8)] 182 | close: ")" [(3, 8), (3, 9)] 183 | ) 184 | ) 185 | 186 | Text 187 | foo@bar() 188 | 189 | S-Expression 190 | (extract_operator [(4, 0), (4, 9)] 191 | lhs: (identifier [(4, 0), (4, 3)]) 192 | operator: "@" [(4, 3), (4, 4)] 193 | rhs: (string [(4, 4), (4, 9)] 194 | open: "\"" [(4, 4), (4, 5)] 195 | content: (string_content [(4, 5), (4, 8)]) 196 | close: "\"" [(4, 8), (4, 9)] 197 | ) 198 | ) 199 | 200 | Text 201 | foo@"bar" 202 | 203 | S-Expression 204 | (extract_operator [(5, 0), (5, 7)] 205 | lhs: (identifier [(5, 0), (5, 3)]) 206 | operator: "@" [(5, 3), (5, 4)] 207 | rhs: (dots [(5, 4), (5, 7)]) 208 | ) 209 | 210 | Text 211 | foo@... 212 | 213 | S-Expression 214 | (extract_operator [(6, 0), (6, 7)] 215 | lhs: (identifier [(6, 0), (6, 3)]) 216 | operator: "@" [(6, 3), (6, 4)] 217 | rhs: (dot_dot_i [(6, 4), (6, 7)]) 218 | ) 219 | 220 | Text 221 | foo@..1 222 | 223 | 224 | # slot no rhs 225 | 226 | Code 227 | node_children_print(node) 228 | Output 229 | S-Expression 230 | (extract_operator [(1, 0), (1, 4)] 231 | lhs: (identifier [(1, 0), (1, 3)]) 232 | operator: "@" [(1, 3), (1, 4)] 233 | ) 234 | 235 | Text 236 | foo@ 237 | 238 | 239 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/_snaps/literals.md: -------------------------------------------------------------------------------- 1 | # identifiers 2 | 3 | Code 4 | node_children_print(node) 5 | Output 6 | S-Expression 7 | (identifier [(1, 0), (1, 3)]) 8 | 9 | Text 10 | foo 11 | 12 | S-Expression 13 | (identifier [(2, 0), (2, 4)]) 14 | 15 | Text 16 | foo2 17 | 18 | S-Expression 19 | (identifier [(3, 0), (3, 7)]) 20 | 21 | Text 22 | foo.bar 23 | 24 | S-Expression 25 | (identifier [(4, 0), (4, 8)]) 26 | 27 | Text 28 | .foo.bar 29 | 30 | S-Expression 31 | (identifier [(5, 0), (5, 15)]) 32 | 33 | Text 34 | .__NAMESPACE__. 35 | 36 | S-Expression 37 | (identifier [(6, 0), (6, 7)]) 38 | 39 | Text 40 | foo_bar 41 | 42 | S-Expression 43 | (identifier [(7, 0), (7, 6)]) 44 | 45 | Text 46 | `_foo` 47 | 48 | S-Expression 49 | (identifier [(8, 0), (8, 13)]) 50 | 51 | Text 52 | `a "literal"` 53 | 54 | S-Expression 55 | (identifier [(9, 0), (10, 15)]) 56 | 57 | Text 58 | `another 59 | literal \` foo` 60 | 61 | S-Expression 62 | (identifier [(11, 0), (12, 1)]) 63 | 64 | Text 65 | `backslash followed by newline \ 66 | ` 67 | 68 | S-Expression 69 | (identifier [(13, 0), (13, 4)]) 70 | 71 | Text 72 | `\`` 73 | 74 | S-Expression 75 | (comment [(14, 0), (14, 18)]) 76 | 77 | Text 78 | # Pipe placeholder 79 | 80 | S-Expression 81 | (identifier [(15, 0), (15, 1)]) 82 | 83 | Text 84 | _ 85 | 86 | S-Expression 87 | (comment [(16, 0), (16, 73)]) 88 | 89 | Text 90 | # Recognized as a single `_foo` identifier, even if invalid R code (#71). 91 | 92 | S-Expression 93 | (identifier [(17, 0), (17, 4)]) 94 | 95 | Text 96 | _foo 97 | 98 | S-Expression 99 | (identifier [(18, 0), (18, 5)]) 100 | 101 | Text 102 | __foo 103 | 104 | S-Expression 105 | (identifier [(19, 0), (19, 5)]) 106 | 107 | Text 108 | _foo_ 109 | 110 | 111 | # comments 112 | 113 | Code 114 | node_children_print(node) 115 | Output 116 | S-Expression 117 | (comment [(1, 0), (1, 12)]) 118 | 119 | Text 120 | # a comment' 121 | 122 | S-Expression 123 | (string [(3, 0), (3, 17)] 124 | open: "'" [(3, 0), (3, 1)] 125 | content: (string_content [(3, 1), (3, 16)]) 126 | close: "'" [(3, 16), (3, 17)] 127 | ) 128 | 129 | Text 130 | '# not a comment' 131 | 132 | S-Expression 133 | (string [(6, 0), (7, 22)] 134 | open: "'" [(6, 0), (6, 1)] 135 | content: (string_content [(6, 1), (7, 21)]) 136 | close: "'" [(7, 21), (7, 22)] 137 | ) 138 | 139 | Text 140 | ' 141 | # still not a comment' 142 | 143 | 144 | # constants 145 | 146 | Code 147 | node_children_print(node) 148 | Output 149 | S-Expression 150 | (true [(1, 0), (1, 4)]) 151 | 152 | Text 153 | TRUE 154 | 155 | S-Expression 156 | (false [(2, 0), (2, 5)]) 157 | 158 | Text 159 | FALSE 160 | 161 | S-Expression 162 | (null [(3, 0), (3, 4)]) 163 | 164 | Text 165 | NULL 166 | 167 | S-Expression 168 | (inf [(4, 0), (4, 3)]) 169 | 170 | Text 171 | Inf 172 | 173 | S-Expression 174 | (nan [(5, 0), (5, 3)]) 175 | 176 | Text 177 | NaN 178 | 179 | S-Expression 180 | (na [(6, 0), (6, 2)] 181 | "NA" [(6, 0), (6, 2)] 182 | ) 183 | 184 | Text 185 | NA 186 | 187 | S-Expression 188 | (na [(7, 0), (7, 8)] 189 | "NA_real_" [(7, 0), (7, 8)] 190 | ) 191 | 192 | Text 193 | NA_real_ 194 | 195 | S-Expression 196 | (na [(8, 0), (8, 13)] 197 | "NA_character_" [(8, 0), (8, 13)] 198 | ) 199 | 200 | Text 201 | NA_character_ 202 | 203 | S-Expression 204 | (na [(9, 0), (9, 11)] 205 | "NA_complex_" [(9, 0), (9, 11)] 206 | ) 207 | 208 | Text 209 | NA_complex_ 210 | 211 | 212 | # integers 213 | 214 | Code 215 | node_children_print(node) 216 | Output 217 | S-Expression 218 | (integer [(1, 0), (1, 6)] 219 | "L" [(1, 5), (1, 6)] 220 | ) 221 | 222 | Text 223 | 12332L 224 | 225 | S-Expression 226 | (integer [(2, 0), (2, 2)] 227 | "L" [(2, 1), (2, 2)] 228 | ) 229 | 230 | Text 231 | 0L 232 | 233 | S-Expression 234 | (integer [(3, 0), (3, 3)] 235 | "L" [(3, 2), (3, 3)] 236 | ) 237 | 238 | Text 239 | 12L 240 | 241 | 242 | # floats 243 | 244 | Code 245 | node_children_print(node) 246 | Output 247 | S-Expression 248 | (float [(1, 0), (1, 3)]) 249 | 250 | Text 251 | .66 252 | 253 | S-Expression 254 | (float [(2, 0), (2, 3)]) 255 | 256 | Text 257 | .11 258 | 259 | S-Expression 260 | (float [(3, 0), (3, 8)]) 261 | 262 | Text 263 | 123.4123 264 | 265 | S-Expression 266 | (float [(4, 0), (4, 5)]) 267 | 268 | Text 269 | .1234 270 | 271 | S-Expression 272 | (binary_operator [(5, 0), (5, 9)] 273 | lhs: (identifier [(5, 0), (5, 1)]) 274 | operator: "<-" [(5, 2), (5, 4)] 275 | rhs: (unary_operator [(5, 5), (5, 9)] 276 | operator: "-" [(5, 5), (5, 6)] 277 | rhs: (float [(5, 6), (5, 9)]) 278 | ) 279 | ) 280 | 281 | Text 282 | x <- -.66 283 | 284 | 285 | # hexadecimal 286 | 287 | Code 288 | node_children_print(node) 289 | Output 290 | S-Expression 291 | (comment [(1, 0), (1, 12)]) 292 | 293 | Text 294 | # `x` vs `X` 295 | 296 | S-Expression 297 | (float [(2, 0), (2, 5)]) 298 | 299 | Text 300 | 0x123 301 | 302 | S-Expression 303 | (float [(3, 0), (3, 5)]) 304 | 305 | Text 306 | 0X123 307 | 308 | S-Expression 309 | (comment [(5, 0), (5, 21)]) 310 | 311 | Text 312 | # Numbers and letters 313 | 314 | S-Expression 315 | (float [(6, 0), (6, 6)]) 316 | 317 | Text 318 | 0xDEAD 319 | 320 | S-Expression 321 | (float [(7, 0), (7, 6)]) 322 | 323 | Text 324 | 0XDEAD 325 | 326 | S-Expression 327 | (float [(8, 0), (8, 7)]) 328 | 329 | Text 330 | 0x1f2F3 331 | 332 | S-Expression 333 | (float [(9, 0), (9, 7)]) 334 | 335 | Text 336 | 0X1f2F3 337 | 338 | S-Expression 339 | (comment [(11, 0), (11, 12)]) 340 | 341 | Text 342 | # `p` vs `P` 343 | 344 | S-Expression 345 | (float [(12, 0), (12, 5)]) 346 | 347 | Text 348 | 0x0p0 349 | 350 | S-Expression 351 | (float [(13, 0), (13, 5)]) 352 | 353 | Text 354 | 0x0P0 355 | 356 | S-Expression 357 | (float [(14, 0), (14, 7)]) 358 | 359 | Text 360 | 0x0p123 361 | 362 | S-Expression 363 | (float [(15, 0), (15, 7)]) 364 | 365 | Text 366 | 0x0P123 367 | 368 | S-Expression 369 | (comment [(17, 0), (17, 13)]) 370 | 371 | Text 372 | # `+` and `-` 373 | 374 | S-Expression 375 | (float [(18, 0), (18, 6)]) 376 | 377 | Text 378 | 0x0p+0 379 | 380 | S-Expression 381 | (float [(19, 0), (19, 6)]) 382 | 383 | Text 384 | 0x0p-0 385 | 386 | S-Expression 387 | (float [(20, 0), (20, 8)]) 388 | 389 | Text 390 | 0x0p+123 391 | 392 | S-Expression 393 | (float [(21, 0), (21, 8)]) 394 | 395 | Text 396 | 0x0p-123 397 | 398 | S-Expression 399 | (comment [(23, 0), (23, 13)]) 400 | 401 | Text 402 | # As integers 403 | 404 | S-Expression 405 | (integer [(24, 0), (24, 6)] 406 | "L" [(24, 5), (24, 6)] 407 | ) 408 | 409 | Text 410 | 0x123L 411 | 412 | S-Expression 413 | (integer [(25, 0), (25, 6)] 414 | "L" [(25, 5), (25, 6)] 415 | ) 416 | 417 | Text 418 | 0X123L 419 | 420 | S-Expression 421 | (integer [(26, 0), (26, 7)] 422 | "L" [(26, 6), (26, 7)] 423 | ) 424 | 425 | Text 426 | 0xDEADL 427 | 428 | S-Expression 429 | (integer [(27, 0), (27, 7)] 430 | "L" [(27, 6), (27, 7)] 431 | ) 432 | 433 | Text 434 | 0XDEADL 435 | 436 | S-Expression 437 | (integer [(28, 0), (28, 6)] 438 | "L" [(28, 5), (28, 6)] 439 | ) 440 | 441 | Text 442 | 0x0p0L 443 | 444 | S-Expression 445 | (integer [(29, 0), (29, 6)] 446 | "L" [(29, 5), (29, 6)] 447 | ) 448 | 449 | Text 450 | 0x0P0L 451 | 452 | S-Expression 453 | (integer [(30, 0), (30, 7)] 454 | "L" [(30, 6), (30, 7)] 455 | ) 456 | 457 | Text 458 | 0x0p+0L 459 | 460 | S-Expression 461 | (integer [(31, 0), (31, 7)] 462 | "L" [(31, 6), (31, 7)] 463 | ) 464 | 465 | Text 466 | 0x0p-0L 467 | 468 | 469 | # scientific notation floats 470 | 471 | Code 472 | node_children_print(node) 473 | Output 474 | S-Expression 475 | (float [(1, 0), (1, 5)]) 476 | 477 | Text 478 | 1e322 479 | 480 | S-Expression 481 | (float [(2, 0), (2, 4)]) 482 | 483 | Text 484 | 1e-3 485 | 486 | S-Expression 487 | (float [(3, 0), (3, 4)]) 488 | 489 | Text 490 | 1e+3 491 | 492 | S-Expression 493 | (float [(4, 0), (4, 6)]) 494 | 495 | Text 496 | 1.8e10 497 | 498 | S-Expression 499 | (float [(5, 0), (5, 5)]) 500 | 501 | Text 502 | 1.e10 503 | 504 | S-Expression 505 | (float [(6, 0), (6, 4)]) 506 | 507 | Text 508 | 1e10 509 | 510 | 511 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/_snaps/missing.md: -------------------------------------------------------------------------------- 1 | # anonymous missing node 2 | 3 | Code 4 | node_children_print(node) 5 | Output 6 | S-Expression 7 | (braced_expression [(1, 0), (4, 0)] 8 | open: "{" [(1, 0), (1, 1)] 9 | body: (braced_expression [(1, 1), (3, 1)] 10 | open: "{" [(1, 1), (1, 2)] 11 | body: (binary_operator [(2, 2), (2, 7)] 12 | lhs: (float [(2, 2), (2, 3)]) 13 | operator: "+" [(2, 4), (2, 5)] 14 | rhs: (float [(2, 6), (2, 7)]) 15 | ) 16 | close: "}" [(3, 0), (3, 1)] 17 | ) 18 | close: "}" MISSING [(4, 0), (4, 0)] 19 | ) 20 | 21 | Text 22 | {{ 23 | 1 + 2 24 | } 25 | 26 | 27 | 28 | # named missing node 29 | 30 | Code 31 | node_children_print(node) 32 | Output 33 | S-Expression 34 | (while_statement [(1, 0), (1, 13)] 35 | "while" [(1, 0), (1, 5)] 36 | open: "(" [(1, 6), (1, 7)] 37 | condition: (binary_operator [(1, 7), (1, 12)] 38 | lhs: (identifier [(1, 7), (1, 8)]) 39 | operator: ">" [(1, 9), (1, 10)] 40 | rhs: (identifier [(1, 11), (1, 12)]) 41 | ) 42 | close: ")" [(1, 12), (1, 13)] 43 | body: (identifier MISSING [(1, 13), (1, 13)]) 44 | ) 45 | 46 | Text 47 | while (a > b) 48 | 49 | 50 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/_snaps/namespace.md: -------------------------------------------------------------------------------- 1 | # namespace 2 | 3 | Code 4 | node_children_print(node) 5 | Output 6 | S-Expression 7 | (namespace_operator [(1, 0), (1, 5)] 8 | lhs: (identifier [(1, 0), (1, 3)]) 9 | operator: "::" [(1, 3), (1, 5)] 10 | ) 11 | 12 | Text 13 | foo:: 14 | 15 | S-Expression 16 | (namespace_operator [(2, 0), (2, 8)] 17 | lhs: (identifier [(2, 0), (2, 3)]) 18 | operator: "::" [(2, 3), (2, 5)] 19 | rhs: (identifier [(2, 5), (2, 8)]) 20 | ) 21 | 22 | Text 23 | foo::bar 24 | 25 | S-Expression 26 | (call [(3, 0), (3, 11)] 27 | function: (namespace_operator [(3, 0), (3, 8)] 28 | lhs: (identifier [(3, 0), (3, 3)]) 29 | operator: "::" [(3, 3), (3, 5)] 30 | rhs: (identifier [(3, 5), (3, 8)]) 31 | ) 32 | arguments: (arguments [(3, 8), (3, 11)] 33 | open: "(" [(3, 8), (3, 9)] 34 | argument: (argument [(3, 9), (3, 10)] 35 | value: (float [(3, 9), (3, 10)]) 36 | ) 37 | close: ")" [(3, 10), (3, 11)] 38 | ) 39 | ) 40 | 41 | Text 42 | foo::bar(1) 43 | 44 | S-Expression 45 | (namespace_operator [(4, 0), (4, 8)] 46 | lhs: (identifier [(4, 0), (4, 3)]) 47 | operator: "::" [(4, 3), (4, 5)] 48 | rhs: (dots [(4, 5), (4, 8)]) 49 | ) 50 | 51 | Text 52 | foo::... 53 | 54 | S-Expression 55 | (namespace_operator [(5, 0), (5, 8)] 56 | lhs: (identifier [(5, 0), (5, 3)]) 57 | operator: "::" [(5, 3), (5, 5)] 58 | rhs: (dot_dot_i [(5, 5), (5, 8)]) 59 | ) 60 | 61 | Text 62 | foo::..1 63 | 64 | S-Expression 65 | (namespace_operator [(6, 0), (6, 8)] 66 | lhs: (dots [(6, 0), (6, 3)]) 67 | operator: "::" [(6, 3), (6, 5)] 68 | rhs: (identifier [(6, 5), (6, 8)]) 69 | ) 70 | 71 | Text 72 | ...::foo 73 | 74 | S-Expression 75 | (namespace_operator [(7, 0), (7, 8)] 76 | lhs: (dot_dot_i [(7, 0), (7, 3)]) 77 | operator: "::" [(7, 3), (7, 5)] 78 | rhs: (identifier [(7, 5), (7, 8)]) 79 | ) 80 | 81 | Text 82 | ..1::foo 83 | 84 | S-Expression 85 | (namespace_operator [(8, 0), (8, 8)] 86 | lhs: (dots [(8, 0), (8, 3)]) 87 | operator: "::" [(8, 3), (8, 5)] 88 | rhs: (dots [(8, 5), (8, 8)]) 89 | ) 90 | 91 | Text 92 | ...::... 93 | 94 | S-Expression 95 | (namespace_operator [(9, 0), (9, 8)] 96 | lhs: (dot_dot_i [(9, 0), (9, 3)]) 97 | operator: "::" [(9, 3), (9, 5)] 98 | rhs: (dot_dot_i [(9, 5), (9, 8)]) 99 | ) 100 | 101 | Text 102 | ..1::..1 103 | 104 | 105 | # namespace internal 106 | 107 | Code 108 | node_children_print(node) 109 | Output 110 | S-Expression 111 | (namespace_operator [(1, 0), (1, 6)] 112 | lhs: (identifier [(1, 0), (1, 3)]) 113 | operator: ":::" [(1, 3), (1, 6)] 114 | ) 115 | 116 | Text 117 | foo::: 118 | 119 | S-Expression 120 | (namespace_operator [(2, 0), (2, 9)] 121 | lhs: (identifier [(2, 0), (2, 3)]) 122 | operator: ":::" [(2, 3), (2, 6)] 123 | rhs: (identifier [(2, 6), (2, 9)]) 124 | ) 125 | 126 | Text 127 | foo:::bar 128 | 129 | S-Expression 130 | (call [(3, 0), (3, 12)] 131 | function: (namespace_operator [(3, 0), (3, 9)] 132 | lhs: (identifier [(3, 0), (3, 3)]) 133 | operator: ":::" [(3, 3), (3, 6)] 134 | rhs: (identifier [(3, 6), (3, 9)]) 135 | ) 136 | arguments: (arguments [(3, 9), (3, 12)] 137 | open: "(" [(3, 9), (3, 10)] 138 | argument: (argument [(3, 10), (3, 11)] 139 | value: (float [(3, 10), (3, 11)]) 140 | ) 141 | close: ")" [(3, 11), (3, 12)] 142 | ) 143 | ) 144 | 145 | Text 146 | foo:::bar(1) 147 | 148 | S-Expression 149 | (namespace_operator [(4, 0), (4, 9)] 150 | lhs: (identifier [(4, 0), (4, 3)]) 151 | operator: ":::" [(4, 3), (4, 6)] 152 | rhs: (dots [(4, 6), (4, 9)]) 153 | ) 154 | 155 | Text 156 | foo:::... 157 | 158 | S-Expression 159 | (namespace_operator [(5, 0), (5, 9)] 160 | lhs: (identifier [(5, 0), (5, 3)]) 161 | operator: ":::" [(5, 3), (5, 6)] 162 | rhs: (dot_dot_i [(5, 6), (5, 9)]) 163 | ) 164 | 165 | Text 166 | foo:::..1 167 | 168 | S-Expression 169 | (namespace_operator [(6, 0), (6, 9)] 170 | lhs: (dots [(6, 0), (6, 3)]) 171 | operator: ":::" [(6, 3), (6, 6)] 172 | rhs: (identifier [(6, 6), (6, 9)]) 173 | ) 174 | 175 | Text 176 | ...:::foo 177 | 178 | S-Expression 179 | (namespace_operator [(7, 0), (7, 9)] 180 | lhs: (dot_dot_i [(7, 0), (7, 3)]) 181 | operator: ":::" [(7, 3), (7, 6)] 182 | rhs: (identifier [(7, 6), (7, 9)]) 183 | ) 184 | 185 | Text 186 | ..1:::foo 187 | 188 | S-Expression 189 | (namespace_operator [(8, 0), (8, 9)] 190 | lhs: (dots [(8, 0), (8, 3)]) 191 | operator: ":::" [(8, 3), (8, 6)] 192 | rhs: (dots [(8, 6), (8, 9)]) 193 | ) 194 | 195 | Text 196 | ...:::... 197 | 198 | S-Expression 199 | (namespace_operator [(9, 0), (9, 9)] 200 | lhs: (dot_dot_i [(9, 0), (9, 3)]) 201 | operator: ":::" [(9, 3), (9, 6)] 202 | rhs: (dot_dot_i [(9, 6), (9, 9)]) 203 | ) 204 | 205 | Text 206 | ..1:::..1 207 | 208 | 209 | # namespace missing rhs 210 | 211 | Code 212 | node_children_print(node) 213 | Output 214 | S-Expression 215 | (comment [(1, 0), (1, 79)]) 216 | 217 | Text 218 | # It's nice that `::` allows an optional RHS and enforces that it can only be a 219 | 220 | S-Expression 221 | (comment [(2, 0), (2, 78)]) 222 | 223 | Text 224 | # string or identifier, so this gives us a pretty clean tree even though it is 225 | 226 | S-Expression 227 | (comment [(3, 0), (3, 17)]) 228 | 229 | Text 230 | # invalid R code. 231 | 232 | S-Expression 233 | (comment [(4, 0), (4, 50)]) 234 | 235 | Text 236 | # https://github.com/r-lib/tree-sitter-r/issues/65 237 | 238 | S-Expression 239 | (call [(5, 0), (5, 16)] 240 | function: (identifier [(5, 0), (5, 7)]) 241 | arguments: (arguments [(5, 7), (5, 16)] 242 | open: "(" [(5, 7), (5, 8)] 243 | argument: (argument [(5, 8), (5, 15)] 244 | value: (namespace_operator [(5, 8), (5, 15)] 245 | lhs: (identifier [(5, 8), (5, 13)]) 246 | operator: "::" [(5, 13), (5, 15)] 247 | ) 248 | ) 249 | close: ")" [(5, 15), (5, 16)] 250 | ) 251 | ) 252 | 253 | Text 254 | library(dplyr::) 255 | 256 | S-Expression 257 | (call [(7, 0), (7, 9)] 258 | function: (identifier [(7, 0), (7, 7)]) 259 | arguments: (arguments [(7, 7), (7, 9)] 260 | open: "(" [(7, 7), (7, 8)] 261 | close: ")" [(7, 8), (7, 9)] 262 | ) 263 | ) 264 | 265 | Text 266 | library() 267 | 268 | S-Expression 269 | (float [(9, 0), (9, 1)]) 270 | 271 | Text 272 | 1 273 | 274 | S-Expression 275 | (float [(10, 0), (10, 1)]) 276 | 277 | Text 278 | 2 279 | 280 | S-Expression 281 | (float [(11, 0), (11, 1)]) 282 | 283 | Text 284 | 3 285 | 286 | 287 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/_snaps/newlines.md: -------------------------------------------------------------------------------- 1 | # leading newlines are not consumed before the `program` node, must start at (0, 0)! (#125) 2 | 3 | Code 4 | node_print(node) 5 | Output 6 | S-Expression 7 | (program [(0, 0), (2, 10)] 8 | (comment [(2, 0), (2, 10)]) 9 | ) 10 | 11 | Text 12 | 13 | 14 | # hi there 15 | 16 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/_snaps/unclosed.md: -------------------------------------------------------------------------------- 1 | # unclosed double quote 2 | 3 | Code 4 | node_children_print(node) 5 | Output 6 | S-Expression 7 | (string [(1, 0), (2, 0)] 8 | open: "\"" [(1, 0), (1, 1)] 9 | content: (string_content [(1, 1), (2, 0)] 10 | (escape_sequence [(1, 1), (1, 3)]) 11 | ) 12 | close: "\"" MISSING [(2, 0), (2, 0)] 13 | ) 14 | 15 | Text 16 | "\" 17 | 18 | 19 | 20 | # unclosed single quote 21 | 22 | Code 23 | node_children_print(node) 24 | Output 25 | S-Expression 26 | (string [(1, 0), (3, 0)] 27 | open: "'" [(1, 0), (1, 1)] 28 | content: (string_content [(1, 1), (3, 0)] 29 | (escape_sequence [(1, 1), (1, 3)]) 30 | ) 31 | close: "'" MISSING [(3, 0), (3, 0)] 32 | ) 33 | 34 | Text 35 | '\' 36 | 37 | 38 | 39 | 40 | # unclosed backtick 41 | 42 | Code 43 | node_children_print(node) 44 | Output 45 | S-Expression 46 | (ERROR [(0, 0), (0, 3)] 47 | (ERROR [(0, 0), (0, 3)]) 48 | ) 49 | 50 | Text 51 | `\` 52 | 53 | 54 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/_snaps/unicode.md: -------------------------------------------------------------------------------- 1 | # unicode identifiers 2 | 3 | Code 4 | node_children_print(node) 5 | Output 6 | S-Expression 7 | (identifier [(1, 0), (1, 6)]) 8 | 9 | Text 10 | 你好 11 | 12 | S-Expression 13 | (identifier [(2, 0), (2, 9)]) 14 | 15 | Text 16 | .你.好. 17 | 18 | S-Expression 19 | (identifier [(3, 0), (3, 9)]) 20 | 21 | Text 22 | .你_好. 23 | 24 | 25 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/fixtures/describe.scm: -------------------------------------------------------------------------------- 1 | (call 2 | function: [ 3 | (identifier) @parent_function 4 | (namespace_operator 5 | lhs: (identifier) @parent_pkg (#eq? @parent_pkg "testthat") 6 | rhs: (identifier) @parent_function 7 | ) 8 | ] (#eq? @parent_function "describe") 9 | arguments: (arguments 10 | (argument 11 | value: (string) @parent_desc 12 | ) 13 | (argument 14 | value: (braced_expression 15 | body: (call 16 | function: [ 17 | (identifier) @function 18 | (namespace_operator 19 | lhs: (identifier) @pkg (#eq? @pkg "testthat") 20 | rhs: (identifier) @function 21 | ) 22 | ] (#eq? @function "it") 23 | arguments: (arguments 24 | (argument 25 | value: (string) @desc 26 | ) 27 | ) 28 | ) @call 29 | ) 30 | ) 31 | ) 32 | ) @parent_call 33 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/fixtures/test_that.scm: -------------------------------------------------------------------------------- 1 | (call 2 | function: [ 3 | (identifier) @function 4 | (namespace_operator 5 | lhs: (identifier) @pkg (#eq? @pkg "testthat") 6 | rhs: (identifier) @function 7 | ) 8 | ] (#eq? @function "test_that") 9 | arguments: [ 10 | (arguments 11 | . (argument value: (string) @desc) . (comma) . (_) . 12 | ) 13 | (arguments 14 | . (_) . (comma) . 15 | (argument 16 | name: (identifier) @param 17 | value: (string) @desc 18 | ) (#eq? @param "desc") 19 | . 20 | ) 21 | ] 22 | ) @call 23 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/helper-print.R: -------------------------------------------------------------------------------- 1 | node_print <- function(x) { 2 | cat_line("S-Expression") 3 | 4 | # Most importantly, no `max_lines` truncation that we'd get 5 | # from the normal print method 6 | treesitter::node_show_s_expression( 7 | x = x, 8 | color_parentheses = FALSE, 9 | color_locations = FALSE 10 | ) 11 | 12 | cat_line() 13 | cat_line("Text") 14 | 15 | text <- treesitter::node_text(x) 16 | cat_line(text) 17 | 18 | invisible(x) 19 | } 20 | 21 | # Always starts with a `program` node that we 22 | # print the children of 23 | node_children_print <- function(x) { 24 | children <- treesitter::node_children(x) 25 | 26 | for (i in seq_along(children)) { 27 | node_print(children[[i]]) 28 | cat_line() 29 | } 30 | 31 | invisible(x) 32 | } 33 | 34 | test_that_tree_sitter <- function(path, children = TRUE) { 35 | env <- parent.frame() 36 | 37 | path <- testthat::test_path(path) 38 | lines <- readLines(path) 39 | 40 | # Find lines that start with `# -----`, this is our divider 41 | # (always add n_lines + 1 to the boundary list) 42 | boundaries <- startsWith(lines, "# -----") 43 | boundaries <- which(boundaries) 44 | boundaries <- unique(c(boundaries, length(lines) + 1L)) 45 | 46 | n_chunks <- length(boundaries) - 1L 47 | 48 | for (i in seq_len(n_chunks)) { 49 | start <- boundaries[[i]] 50 | end <- boundaries[[i + 1L]] 51 | 52 | # The description should be the comment on the line after the `# -----` 53 | desc <- lines[[start + 1L]] 54 | desc <- sub("# ", "", desc, fixed = TRUE) 55 | 56 | start <- start + 2L 57 | end <- pmax(end - 1L, start) 58 | 59 | # Text is everything between this divider and the next one 60 | text <- lines[seq(start, end)] 61 | text <- paste0(text, collapse = "\n") 62 | 63 | # Parse and snapshot 64 | expr <- substitute( 65 | testthat::test_that(desc, { 66 | testthat::skip_if_not_installed("treesitter") 67 | 68 | language <- language() 69 | parser <- treesitter::parser(language) 70 | 71 | tree <- treesitter::parser_parse(parser, text) 72 | node <- treesitter::tree_root_node(tree) 73 | 74 | if (children) { 75 | testthat::expect_snapshot(node_children_print(node)) 76 | } else { 77 | testthat::expect_snapshot(node_print(node)) 78 | } 79 | }), 80 | list(desc = desc, text = text, children = children) 81 | ) 82 | 83 | eval(expr, env) 84 | } 85 | } 86 | 87 | cat_line <- function(...) { 88 | out <- paste0(..., collapse = "\n") 89 | cat(out, "\n", sep = "", file = stdout(), append = TRUE) 90 | } 91 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/helper-query-describe.R: -------------------------------------------------------------------------------- 1 | expect_describe_it_captures <- function(parent_desc, desc, captures) { 2 | skip_if_not_installed("treesitter") 3 | 4 | # make sure we captured something 5 | expect_gt(length(captures$node), 0) 6 | 7 | # the capture names that should always be present 8 | expect_contains( 9 | captures$name, 10 | c( 11 | "parent_call", 12 | "parent_function", 13 | "parent_desc", 14 | "call", 15 | "function", 16 | "desc" 17 | ) 18 | ) 19 | 20 | expect_equal( 21 | treesitter::node_text(captures[["node"]][[which( 22 | captures$name == "parent_function" 23 | )]]), 24 | "describe" 25 | ) 26 | expect_equal( 27 | treesitter::node_text(captures[["node"]][[which( 28 | captures$name == "parent_desc" 29 | )]]), 30 | parent_desc 31 | ) 32 | expect_equal( 33 | treesitter::node_text(captures[["node"]][[which( 34 | captures$name == "function" 35 | )]]), 36 | "it" 37 | ) 38 | expect_equal( 39 | treesitter::node_text(captures[["node"]][[which(captures$name == "desc")]]), 40 | desc 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/helper-query-testthat.R: -------------------------------------------------------------------------------- 1 | expect_test_that_captures <- function(code_source, desc, captures) { 2 | skip_if_not_installed("treesitter") 3 | 4 | # make sure we captured something 5 | expect_gt(length(captures$node), 0) 6 | 7 | # the capture names that should always be present 8 | expect_contains(captures$name, c("call", "function", "desc")) 9 | # other captures, such as "pkg" or "param" could also be here 10 | 11 | expect_equal( 12 | treesitter::node_text(captures[["node"]][[which(captures$name == "call")]]), 13 | trimws(code_source) 14 | ) 15 | expect_equal( 16 | treesitter::node_text(captures[["node"]][[which( 17 | captures$name == "function" 18 | )]]), 19 | "test_that" 20 | ) 21 | expect_equal( 22 | treesitter::node_text(captures[["node"]][[which(captures$name == "desc")]]), 23 | desc 24 | ) 25 | if ("pkg" %in% captures$name) { 26 | expect_equal( 27 | treesitter::node_text(captures[["node"]][[which( 28 | captures$name == "pkg" 29 | )]]), 30 | "testthat" 31 | ) 32 | } 33 | if ("param" %in% captures$name) { 34 | expect_equal( 35 | treesitter::node_text(captures[["node"]][[which( 36 | captures$name == "param" 37 | )]]), 38 | "desc" 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/helper-query.R: -------------------------------------------------------------------------------- 1 | read_fixture <- function(file) { 2 | paste(readLines(test_path("fixtures", file)), collapse = '') 3 | } 4 | 5 | get_captures <- function(code_source, query_source) { 6 | skip_if_not_installed("treesitter") 7 | language <- language() 8 | parser <- treesitter::parser(language) 9 | tree <- treesitter::parser_parse(parser, code_source) 10 | node <- treesitter::tree_root_node(tree) 11 | query <- treesitter::query(language, query_source) 12 | treesitter::query_captures(query, node) 13 | } 14 | 15 | get_matches <- function(code_source, query_source) { 16 | skip_if_not_installed("treesitter") 17 | language <- language() 18 | parser <- treesitter::parser(language) 19 | tree <- treesitter::parser_parse(parser, code_source) 20 | node <- treesitter::tree_root_node(tree) 21 | query <- treesitter::query(language, query_source) 22 | treesitter::query_matches(query, node)[[1]] 23 | } 24 | 25 | expect_top_level <- function(node) { 26 | skip_if_not_installed("treesitter") 27 | expect_equal( 28 | treesitter::node_type(treesitter::node_parent(node)), 29 | "program" 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/references/calls.R: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # subset 3 | 4 | foo[bar] 5 | foo[1, 2] 6 | foo[1, ] 7 | foo[1,, ] 8 | foo[1,,2] 9 | foo[x=1,,y=3,4] 10 | foo[] 11 | 12 | # ------------------------------------------------------------------------------ 13 | # subset2 14 | 15 | foo[[x]] 16 | foo[[x, y]] 17 | foo[[x,]] 18 | foo[[x,,]] 19 | foo[[x,,y]] 20 | foo[[]] 21 | 22 | # ------------------------------------------------------------------------------ 23 | # subset and subset2 precedence 24 | 25 | a[[b[1]]] 26 | a[b[[1]]] 27 | 28 | # ------------------------------------------------------------------------------ 29 | # switch 30 | 31 | switch(foo, 32 | x = 1, 33 | "y" = 2, 34 | z = , 35 | 3 36 | ) 37 | 38 | # ------------------------------------------------------------------------------ 39 | # calls 40 | 41 | f() 42 | f(x) 43 | f(1+1) 44 | f(1 ~ 1) 45 | f(x, ) 46 | f(x,,y) 47 | f(x, y) 48 | f(x, y = 2) 49 | f(x = 1 + 1) 50 | f(x, y =) 51 | f(f2(x, y)) 52 | f(,) 53 | f(x,) 54 | f(,y) 55 | f(x=,) 56 | f("x"=,) 57 | f(,y=) 58 | 59 | # Dots as unnamed and named argument 60 | f(...) 61 | f(, ..., 1) 62 | f(... = 1) 63 | f(... = ,) 64 | f(... = ...) 65 | 66 | # `..i` as unnamed and named argument 67 | f(..1) 68 | f(, ..1, 1) 69 | f(..1 = 1) 70 | f(..1 = ,) 71 | f(..1 = ..1) 72 | 73 | # ------------------------------------------------------------------------------ 74 | # not a call, subset, or subset2 75 | 76 | f 77 | (x) 78 | 79 | foo 80 | [bar] 81 | 82 | foo 83 | [[x]] 84 | 85 | # ------------------------------------------------------------------------------ 86 | # not a call, subset, or subset2 due to sequential arguments 87 | 88 | f(x y) 89 | foo[x y] 90 | foo[[x y]] 91 | 92 | f(x, y z) 93 | foo[x, y z] 94 | foo[[x, y z]] 95 | 96 | f(, x y) 97 | foo[, x y] 98 | foo[[, x y]] 99 | 100 | # ------------------------------------------------------------------------------ 101 | # braced expression 102 | 103 | {} 104 | 105 | {1} 106 | 107 | {1; 2} 108 | 109 | {1; 110 | 2} 111 | 112 | {1 113 | 2 114 | } 115 | 116 | { 117 | 1 118 | 2 119 | } 120 | 121 | # https://github.com/r-lib/tree-sitter-r/issues/44 122 | { 123 | 1 124 | 2 125 | 3 126 | } 127 | 128 | # ------------------------------------------------------------------------------ 129 | # parenthesized expression 130 | 131 | (1) 132 | ((1)) 133 | (1 + 1) 134 | (fn(a, b)) 135 | fn((a), (b)) 136 | (function() { 137 | body 138 | }) 139 | 140 | # ------------------------------------------------------------------------------ 141 | # not a parenthesized expression 1 142 | 143 | () 144 | 145 | # ------------------------------------------------------------------------------ 146 | # not a parenthesized expression 2 147 | 148 | ( 149 | 1 150 | 2 151 | ) 152 | 153 | # ------------------------------------------------------------------------------ 154 | # not a parenthesized expression 3 155 | 156 | (1; 2) 157 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/references/control-flow.R: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # if 3 | 4 | if (x) 5 | log(y) 6 | 7 | if (a.b) { 8 | log(c) 9 | d 10 | } 11 | 12 | if ( 13 | x < 14 | y 15 | ) { 16 | z 17 | } 18 | 19 | # ------------------------------------------------------------------------------ 20 | # if else 21 | 22 | if (x) 23 | y else if (a) 24 | b 25 | 26 | if (x) 27 | y else if (a) 28 | b else d 29 | 30 | if (a) { 31 | c 32 | d 33 | } else { 34 | e 35 | } 36 | 37 | # ------------------------------------------------------------------------------ 38 | # complex if statements 39 | 40 | # Invalid at top level due to newline before `else`, so not a real if statement 41 | if (TRUE) { 42 | 1 43 | } 44 | else { 45 | 2 46 | } 47 | 48 | # Invalid for same reason as above 49 | if (TRUE) 50 | 1 51 | else 52 | 2 53 | 54 | # Valid inside `{` only due to special `else` handling with newlines 55 | { 56 | if (TRUE) { 57 | 1 58 | } 59 | else { 60 | 2 61 | } 62 | } 63 | 64 | # Valid with comments in special newline territory 65 | { 66 | if (TRUE) { 67 | 1 68 | } 69 | # hi there 70 | 71 | # another one! 72 | 73 | else { 74 | 2 75 | } 76 | } 77 | 78 | # Valid. This test ensures we handle the newline after `1 + 1` correctly (#125). 79 | { 80 | if (TRUE) 81 | 82 | 1 + 1 83 | } 84 | 85 | # Valid. Newlines are allowed between the `else` and the `alternative`, even at top level (#141). 86 | if (TRUE) { 87 | 1 88 | } else 89 | { 90 | 2 91 | } 92 | 93 | # Valid. Same as above but in `{ }` so it is valid no matter where the newlines are. 94 | { 95 | if (TRUE) { 96 | 1 97 | } else 98 | { 99 | 2 100 | } 101 | } 102 | 103 | # Valid. Newlines and comments are allowed between the `else` and the `alternative`, even at top level. 104 | if (TRUE) { 105 | 1 106 | } else 107 | 108 | # do this alternative 109 | { 110 | 2 111 | } 112 | 113 | # Valid. Newlines are allowed between the `else` and the `alternative`, even at top level. 114 | if (TRUE) 1 else 115 | 2 116 | 117 | # ------------------------------------------------------------------------------ 118 | # for 119 | 120 | for (x in y) 121 | f 122 | 123 | for (x in 5:6) { 124 | for (y in ys) { 125 | z 126 | } 127 | } 128 | 129 | for (x in y) for (y in z) x + y 130 | 131 | # `...` as the `variable` 132 | for (... in 1:2) print(get("...")) 133 | 134 | # `..i` as the `variable` 135 | for (..1 in 1:2) print(get("..1")) 136 | 137 | # ------------------------------------------------------------------------------ 138 | # for no body 139 | 140 | for (i in 1:5) 141 | 142 | # ------------------------------------------------------------------------------ 143 | # while 144 | 145 | while(TRUE) 146 | bar 147 | 148 | while(x > 0) 149 | x <- x - 1 150 | 151 | while(TRUE) 152 | break 153 | 154 | while(TRUE) 155 | next 156 | 157 | # ------------------------------------------------------------------------------ 158 | # while no body 159 | 160 | while (a < b) 161 | 162 | # ------------------------------------------------------------------------------ 163 | # repeat 164 | 165 | repeat 1 166 | 167 | # ------------------------------------------------------------------------------ 168 | # repeat no body 169 | 170 | repeat 171 | 172 | # dummy comment to retain a newline after the `repeat` (can remove if we add another test) 173 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/references/delimiter.R: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # closing brace 3 | 4 | } 5 | 6 | # ------------------------------------------------------------------------------ 7 | # closing parenthesis 8 | 9 | ) 10 | 11 | # ------------------------------------------------------------------------------ 12 | # closing bracket 13 | 14 | ] 15 | 16 | # ------------------------------------------------------------------------------ 17 | # opening brace, closing parenthesis 18 | 19 | # Parenthesis is "not valid" so it isn't matched by the external scanner 20 | {) 21 | 22 | # ------------------------------------------------------------------------------ 23 | # opening parenthesis, closing brace 24 | 25 | (} 26 | 27 | # ------------------------------------------------------------------------------ 28 | # opening parenthesis, closing bracket 29 | 30 | (] 31 | 32 | # ------------------------------------------------------------------------------ 33 | # opening bracket2, unmatched closing bracket 34 | 35 | x[[2] 36 | 37 | # ------------------------------------------------------------------------------ 38 | # opening bracket and bracket2, unmatched closing bracket 39 | 40 | x[y[[2] 41 | 42 | # ------------------------------------------------------------------------------ 43 | # opening bracket2 and bracket, matched closing bracket 44 | 45 | x[[y[2] 46 | 47 | # ------------------------------------------------------------------------------ 48 | # opening bracket2 and bracket, unmatched closing bracket 49 | 50 | x[[y[2]] 51 | 52 | # ------------------------------------------------------------------------------ 53 | # for no closing `)` 54 | 55 | for (i in vec 56 | 57 | 1 + 1 58 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/references/extract.R: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # dollar 3 | 4 | foo$bar 5 | foo$bar$baz 6 | foo$bar@baz 7 | foo$bar() 8 | foo$"bar" 9 | foo$bar()$baz[[1]]$bam 10 | foo$... 11 | foo$..1 12 | 13 | # ------------------------------------------------------------------------------ 14 | # dollar no rhs 15 | 16 | foo$ 17 | 18 | # ------------------------------------------------------------------------------ 19 | # slot 20 | 21 | foo@bar 22 | foo@bar$baz 23 | foo@bar() 24 | foo@"bar" 25 | foo@... 26 | foo@..1 27 | 28 | # ------------------------------------------------------------------------------ 29 | # slot no rhs 30 | 31 | foo@ 32 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/references/functions.R: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # functions 3 | 4 | function() 1 5 | function() {} 6 | function(arg1, arg2) { 7 | arg2 8 | } 9 | 10 | function(x, y) return(y) 11 | 12 | function(arg1, arg2 = 2) {} 13 | 14 | function(x, 15 | y, 16 | z = 3) { 17 | 18 | } 19 | 20 | function() 21 | 22 | 23 | 1 24 | 25 | function 26 | () 1 + 1 27 | 28 | function 29 | 30 | () 1 + 1 31 | 32 | function 33 | 34 | # this important comment 35 | 36 | () 1 + 1 37 | 38 | function() function() {} 39 | 40 | function(x = function() {}) {} 41 | 42 | # With no intermediate `{` scope 43 | function() for(i in 1:5) i 44 | 45 | # ------------------------------------------------------------------------------ 46 | # function dots and dot dot i 47 | 48 | # Dots as parameter without default 49 | function(...) { list(...) } 50 | 51 | # Dots as parameter with default 52 | function(... = 1) { get("...") } 53 | 54 | # `..i` as parameter without default 55 | function(..1) { get("..1") } 56 | 57 | # `..i` as parameter with default 58 | function(..1 = 1) { get("..1") } 59 | 60 | # Miscellaneous 61 | function(x, ...) f(...) 62 | function(x, ...) ..1 + ..2 63 | 64 | # ------------------------------------------------------------------------------ 65 | # function no body 66 | 67 | function(x, y) 68 | 69 | # ------------------------------------------------------------------------------ 70 | # function no body with assignment 71 | 72 | x <- function(x, y) 73 | 74 | # ------------------------------------------------------------------------------ 75 | # function no body inside another function 76 | 77 | function(x = function()) {} 78 | 79 | # ------------------------------------------------------------------------------ 80 | # lambda function 81 | 82 | a <- \(arg) arg 83 | b <- \(arg1, arg2) paste0(arg1, arg2) 84 | c <- \(fun, ...) fun(...) 85 | 1:3 |> {\(x, y = 1) x + y}() |> {\(x) sum(x)}() 86 | {\(a = 1) a + 1}() 87 | \() 1 + 2 88 | \() 89 | 90 | 1 + 2 91 | 92 | # Not currently allowed by the parser, but we think it will be and is just an oversight. 93 | # `'\\'` would need to be included alongside `FUNCTION` here https://github.com/wch/r-source/blob/802121c877837926a6bc2a930b3da749b537258b/src/main/gram.y#L3898-L3901. 94 | \ 95 | () 1 + 1 96 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/references/literals.R: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # identifiers 3 | 4 | foo 5 | foo2 6 | foo.bar 7 | .foo.bar 8 | .__NAMESPACE__. 9 | foo_bar 10 | `_foo` 11 | `a "literal"` 12 | `another 13 | literal \` foo` 14 | `backslash followed by newline \ 15 | ` 16 | `\`` 17 | # Pipe placeholder 18 | _ 19 | # Recognized as a single `_foo` identifier, even if invalid R code (#71). 20 | _foo 21 | __foo 22 | _foo_ 23 | 24 | # ------------------------------------------------------------------------------ 25 | # comments 26 | 27 | # a comment' 28 | 29 | '# not a comment' 30 | 31 | 32 | ' 33 | # still not a comment' 34 | 35 | # ------------------------------------------------------------------------------ 36 | # constants 37 | 38 | TRUE 39 | FALSE 40 | NULL 41 | Inf 42 | NaN 43 | NA 44 | NA_real_ 45 | NA_character_ 46 | NA_complex_ 47 | 48 | # ------------------------------------------------------------------------------ 49 | # integers 50 | 51 | 12332L 52 | 0L 53 | 12L 54 | 55 | # ------------------------------------------------------------------------------ 56 | # floats 57 | 58 | .66 59 | .11 60 | 123.4123 61 | .1234 62 | x <- -.66 63 | 64 | # ------------------------------------------------------------------------------ 65 | # hexadecimal 66 | 67 | # `x` vs `X` 68 | 0x123 69 | 0X123 70 | 71 | # Numbers and letters 72 | 0xDEAD 73 | 0XDEAD 74 | 0x1f2F3 75 | 0X1f2F3 76 | 77 | # `p` vs `P` 78 | 0x0p0 79 | 0x0P0 80 | 0x0p123 81 | 0x0P123 82 | 83 | # `+` and `-` 84 | 0x0p+0 85 | 0x0p-0 86 | 0x0p+123 87 | 0x0p-123 88 | 89 | # As integers 90 | 0x123L 91 | 0X123L 92 | 0xDEADL 93 | 0XDEADL 94 | 0x0p0L 95 | 0x0P0L 96 | 0x0p+0L 97 | 0x0p-0L 98 | 99 | # ------------------------------------------------------------------------------ 100 | # scientific notation floats 101 | 102 | 1e322 103 | 1e-3 104 | 1e+3 105 | 1.8e10 106 | 1.e10 107 | 1e10 108 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/references/miscellaneous.R: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # dollar, at, namespace, namespace internal with expression rhs 3 | 4 | # These nodes allows an optional RHS, and the RHS must be a string/identifier, 5 | # so we nicely get a true node here alongside the braces. Even if that's not 6 | # parsable R code, it's useful for completions and highlighting. 7 | 8 | foo${bar} 9 | foo@{bar} 10 | foo::{bar} 11 | foo:::{bar} 12 | 13 | # ------------------------------------------------------------------------------ 14 | # dollar, at, namespace, namespace internal with `if` rhs 15 | 16 | # The RHS's of these nodes are restricted to strings and identifiers, and the RHS 17 | # is optional. This ends up trying to match to an `if_statement` node, leaving the RHS 18 | # empty, and then the `if_statement` node errors because it doesn't have a `(`. This 19 | # is actually pretty decent behavior. 20 | 21 | foo$if 22 | foo@if 23 | foo::if 24 | foo:::if 25 | 26 | # ------------------------------------------------------------------------------ 27 | # dollar, at, namespace, namespace internal with newline before operator 28 | 29 | # Newlines are not allowed before the operator, so none of these are valid 30 | x 31 | $y 32 | 33 | x 34 | @y 35 | 36 | x 37 | ::y 38 | 39 | x 40 | :::y 41 | 42 | # ------------------------------------------------------------------------------ 43 | # complex expressions 44 | 45 | repeat if (1) TRUE else repeat 42 46 | if (TRUE) if (FALSE) 2 else NULL 47 | a::b$c[[d]] <- e 48 | TRUE ~ FALSE ~ NULL ? NA ? NaN 49 | if (TRUE) FALSE 50 | else NA 51 | (if (TRUE) FALSE 52 | else NA) 53 | a = TRUE ? FALSE 54 | TRUE <- FALSE = NA 55 | TRUE <- FALSE ? NA 56 | TRUE = FALSE ? NA 57 | TRUE ? FALSE = NA 58 | 59 | # ------------------------------------------------------------------------------ 60 | # precedence 61 | 62 | A$"B"^NA 63 | a::b$c 64 | a$b?c 65 | 66 | # ------------------------------------------------------------------------------ 67 | # newlines 68 | 69 | apple 70 | (banana) 71 | 72 | { 73 | apple 74 | (banana) 75 | } 76 | 77 | ( 78 | apple 79 | (banana) 80 | ) 81 | 82 | # ------------------------------------------------------------------------------ 83 | # motivation for not having `unmatched_delimiter` 1 (#90) 84 | 85 | # `if ()` is invalid R code because there isn't a `condition` between the two 86 | # `()`. We don't want tree-sitter to think `)` is an `unmatched_delimiter` 87 | # expression that it can accept as `condition`, we instead want to report an 88 | # error as close to the `()` as is possible. 89 | 90 | foo <- function() { 91 | if () 92 | 93 | 1 + 1 94 | } 95 | 96 | # ------------------------------------------------------------------------------ 97 | # motivation for not having `unmatched_delimiter` 2 (#90) 98 | 99 | # Same argument as above, but in this case `unmatched_delimiter` made the entire 100 | # program show as ERROR with no sub-ERROR that would allow us to narrow the 101 | # scope. 102 | 103 | blah('foo', 104 | list( 105 | foo = function() { 106 | if () 107 | }, 108 | 109 | foo2 = function() { 110 | 111 | } 112 | ) 113 | ) 114 | 115 | # ------------------------------------------------------------------------------ 116 | # motivation for not having `unmatched_delimiter` 3 (#90) 117 | 118 | # In this case we ideally want the `{}` to be a matching pair, and for there to 119 | # be a MISSING rhs of the `+` operator. This has proven difficult though, so 120 | # instead we end up reporting the `}` as a syntax error using a narrowly scoped 121 | # ERROR node. 122 | 123 | { 124 | ggplot() + 125 | } 126 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/references/missing.R: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # anonymous missing node 3 | 4 | {{ 5 | 1 + 2 6 | } 7 | 8 | # ------------------------------------------------------------------------------ 9 | # named missing node 10 | 11 | while (a > b) 12 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/references/namespace.R: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # namespace 3 | 4 | foo:: 5 | foo::bar 6 | foo::bar(1) 7 | foo::... 8 | foo::..1 9 | ...::foo 10 | ..1::foo 11 | ...::... 12 | ..1::..1 13 | 14 | # ------------------------------------------------------------------------------ 15 | # namespace internal 16 | 17 | foo::: 18 | foo:::bar 19 | foo:::bar(1) 20 | foo:::... 21 | foo:::..1 22 | ...:::foo 23 | ..1:::foo 24 | ...:::... 25 | ..1:::..1 26 | 27 | # ------------------------------------------------------------------------------ 28 | # namespace missing rhs 29 | 30 | # It's nice that `::` allows an optional RHS and enforces that it can only be a 31 | # string or identifier, so this gives us a pretty clean tree even though it is 32 | # invalid R code. 33 | # https://github.com/r-lib/tree-sitter-r/issues/65 34 | library(dplyr::) 35 | 36 | library() 37 | 38 | 1 39 | 2 40 | 3 41 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/references/newlines.R: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # leading newlines are not consumed before the `program` node, must start at (0, 0)! (#125) 3 | 4 | 5 | # hi there 6 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/references/operators.R: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # relational 3 | 4 | a == b 5 | a > b 6 | a < b 7 | a >= b 8 | a <= b 9 | a != b 10 | 11 | a == 12 | b 13 | 14 | # ------------------------------------------------------------------------------ 15 | # arithmetic 16 | 17 | a + b 18 | a - b 19 | a * b 20 | a / b 21 | a ^ b 22 | a ** b 23 | 24 | a + 25 | b 26 | 27 | a * 28 | b 29 | 30 | # ------------------------------------------------------------------------------ 31 | # not an arithmetic operator 32 | 33 | a 34 | + b 35 | 36 | # ------------------------------------------------------------------------------ 37 | # unary 38 | 39 | !a 40 | +a 41 | -a 42 | foo(!a, +b) 43 | foo(-a, bar) 44 | 45 | ! 46 | a 47 | - b 48 | 49 | # ------------------------------------------------------------------------------ 50 | # precedence 51 | 52 | 2+a*2 53 | 2+a+2 54 | !a + !b 55 | a <= 2 && 2 >= d 56 | a[1] <- foo || bar 57 | a && b(c) && d 58 | val <- foo %>% bar(1) %>% baz() 59 | 60 | # ------------------------------------------------------------------------------ 61 | # specials 62 | 63 | x %% y 64 | x %/% y 65 | x %+% y 66 | x %>% y 67 | x %>% 2 %>% z 68 | x %some text% y 69 | x %//% y 70 | 71 | # ------------------------------------------------------------------------------ 72 | # pipe 73 | 74 | x |> print() 75 | 76 | x |> foo() %>% bar() |> baz() 77 | 78 | x |> foo() |> bar() + baz() 79 | 80 | x |> {function(x) x}() 81 | 82 | # ------------------------------------------------------------------------------ 83 | # pipe placeholder 84 | 85 | foo |> bar(x, y = _) 86 | foo |> bar() |> baz(data = _) 87 | 88 | 89 | # ------------------------------------------------------------------------------ 90 | # assignment 91 | 92 | x <- 1 93 | x = 1 94 | x := 1 95 | x <<- 1 96 | 1 ->> x 97 | 1 -> x 98 | x <- y(1) 99 | y(1) -> x 100 | 101 | # ------------------------------------------------------------------------------ 102 | # colon 103 | 104 | 1:2 105 | (1 + 1):-5 106 | 107 | # ------------------------------------------------------------------------------ 108 | # formulas 109 | 110 | ~x 111 | y~x 112 | 113 | # ------------------------------------------------------------------------------ 114 | # help 115 | 116 | a ? b 117 | a ? b <- 1 118 | ?a 119 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/references/strings.R: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # strings 3 | 4 | "foo" 5 | "foo 6 | bar" 7 | "foo 8 | 9 | 10 | bar" 11 | "#" 12 | 13 | 'foo' 14 | '#' 15 | 16 | # ------------------------------------------------------------------------------ 17 | # strings without content 18 | 19 | "" 20 | '' 21 | 22 | # ------------------------------------------------------------------------------ 23 | # strings with escape sequences 24 | 25 | "\\" 26 | "\ " 27 | "\n" 28 | "\t" 29 | "\r" 30 | "\"" 31 | '\'' 32 | 33 | "\0" 34 | "\1" 35 | "\7" 36 | "\12" 37 | "\123" 38 | "\1234" 39 | 40 | "\x1" 41 | "\x01" 42 | "\x012" 43 | 44 | "\u1" 45 | "\u01" 46 | "\u001" 47 | "\u0001" 48 | "\u00011" 49 | 50 | "\u{1}" 51 | "\u{01}" 52 | "\u{001}" 53 | "\u{0001}" 54 | 55 | "\U{1}" 56 | "\U{01}" 57 | "\U{001}" 58 | "\U{0001}" 59 | "\U{00001}" 60 | "\U{000001}" 61 | "\U{0000001}" 62 | "\U{00000001}" 63 | 64 | "\U1" 65 | "\U01" 66 | "\U001" 67 | "\U0001" 68 | "\U00001" 69 | "\U000001" 70 | "\U0000001" 71 | "\U00000001" 72 | "\U000000011" 73 | 74 | "foo\"bar" 75 | "foo\"bar\"" 76 | "foo\ " 77 | "foo\ 78 | " 79 | 80 | # ------------------------------------------------------------------------------ 81 | # invalid strings 1 82 | 83 | "\u{00001}" 84 | 85 | # ------------------------------------------------------------------------------ 86 | # invalid strings 2 87 | 88 | "\U{000000001}" 89 | 90 | # ------------------------------------------------------------------------------ 91 | # invalid strings 3 92 | 93 | "\8" 94 | 95 | # ------------------------------------------------------------------------------ 96 | # invalid strings 4 97 | 98 | "\x" 99 | 100 | # ------------------------------------------------------------------------------ 101 | # raw strings 102 | 103 | r"(raw string)" 104 | R"{another raw string}" 105 | R"--[yet another ]- raw string]--" 106 | 107 | r"(")" 108 | r"("")" 109 | r"(')" 110 | r"('')" 111 | 112 | r"-(-)-" 113 | r"-()-)-" 114 | r"--()-")--" 115 | 116 | r"( () )" 117 | r"(())" 118 | r"( ())" 119 | r"(() )" 120 | r"-())-" 121 | r"-(())-)-" 122 | 123 | r"(raw 124 | string 125 | )" 126 | 127 | r() 128 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/references/unclosed.R: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # unclosed double quote 3 | 4 | "\" 5 | 6 | # ------------------------------------------------------------------------------ 7 | # unclosed single quote 8 | 9 | '\' 10 | 11 | 12 | # ------------------------------------------------------------------------------ 13 | # unclosed backtick 14 | `\` 15 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/references/unicode.R: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # unicode identifiers 3 | 4 | 你好 5 | .你.好. 6 | .你_好. 7 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-abi.R: -------------------------------------------------------------------------------- 1 | test_that("abi version hasn't changed", { 2 | expect_identical(abi(), 14L) 3 | }) 4 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-calls.R: -------------------------------------------------------------------------------- 1 | test_that_tree_sitter("references/calls.R") 2 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-control-flow.R: -------------------------------------------------------------------------------- 1 | test_that_tree_sitter("references/control-flow.R") 2 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-delimiter.R: -------------------------------------------------------------------------------- 1 | test_that_tree_sitter("references/delimiter.R") 2 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-extract.R: -------------------------------------------------------------------------------- 1 | test_that_tree_sitter("references/extract.R") 2 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-functions.R: -------------------------------------------------------------------------------- 1 | test_that_tree_sitter("references/functions.R") 2 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-literals.R: -------------------------------------------------------------------------------- 1 | test_that_tree_sitter("references/literals.R") 2 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-miscellaneous.R: -------------------------------------------------------------------------------- 1 | test_that_tree_sitter("references/miscellaneous.R") 2 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-missing.R: -------------------------------------------------------------------------------- 1 | test_that_tree_sitter("references/missing.R") 2 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-namespace.R: -------------------------------------------------------------------------------- 1 | test_that_tree_sitter("references/namespace.R") 2 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-newlines.R: -------------------------------------------------------------------------------- 1 | test_that_tree_sitter("references/newlines.R", children = FALSE) 2 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-operators.R: -------------------------------------------------------------------------------- 1 | test_that_tree_sitter("references/operators.R") 2 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-query-describe.R: -------------------------------------------------------------------------------- 1 | test_that("describe(it()) with 2 it()s can be matched", { 2 | # first example for testthat::describe() 3 | code_source <- ' 4 | describe("matrix()", { 5 | it("can be multiplied by a scalar", { 6 | m1 <- matrix(1:4, 2, 2) 7 | m2 <- m1 * 2 8 | expect_equal(matrix(1:4 * 2, 2, 2), m2) 9 | }) 10 | it("can have not yet tested specs") 11 | }) 12 | ' 13 | matches <- get_matches(code_source, read_fixture("describe.scm")) 14 | 15 | # there are 2 it() calls 16 | expect_length(matches, 2) 17 | 18 | it_one <- matches[[1]] 19 | expect_describe_it_captures( 20 | "\"matrix()\"", 21 | "\"can be multiplied by a scalar\"", 22 | it_one 23 | ) 24 | expect_top_level( 25 | it_one[["node"]][[which(it_one$name == "parent_call")]] 26 | ) 27 | 28 | it_two <- matches[[2]] 29 | expect_describe_it_captures( 30 | "\"matrix()\"", 31 | "\"can have not yet tested specs\"", 32 | it_two 33 | ) 34 | expect_top_level( 35 | it_two[["node"]][[which(it_two$name == "parent_call")]] 36 | ) 37 | }) 38 | 39 | test_that("nested describe() can be matched", { 40 | # second example for testthat::describe() 41 | code_source <- ' 42 | describe("math library", { 43 | describe("addition()", { 44 | it("can add two numbers", { 45 | expect_equal(1 + 1, addition(1, 1)) 46 | }) 47 | }) 48 | describe("division()", { 49 | it("can divide two numbers", { 50 | expect_equal(10 / 2, division(10, 2)) 51 | }) 52 | it("can handle division by 0") #not yet implemented 53 | }) 54 | }) 55 | ' 56 | matches <- get_matches(code_source, read_fixture("describe.scm")) 57 | 58 | # there are 3 it() calls 59 | expect_length(matches, 3) 60 | 61 | expect_describe_it_captures( 62 | "\"addition()\"", 63 | "\"can add two numbers\"", 64 | matches[[1]] 65 | ) 66 | 67 | expect_describe_it_captures( 68 | "\"division()\"", 69 | "\"can divide two numbers\"", 70 | matches[[2]] 71 | ) 72 | 73 | expect_describe_it_captures( 74 | "\"division()\"", 75 | "\"can handle division by 0\"", 76 | matches[[3]] 77 | ) 78 | }) 79 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-query-testthat.R: -------------------------------------------------------------------------------- 1 | test_that("one-liner test_that() call is matched", { 2 | code_source <- 'test_that("desc one-liner", expect_true(TRUE))' 3 | captures <- get_captures(code_source, read_fixture("test_that.scm")) 4 | 5 | expect_test_that_captures( 6 | code_source, 7 | "\"desc one-liner\"", 8 | captures 9 | ) 10 | expect_top_level(captures[["node"]][[1]]) 11 | }) 12 | 13 | test_that("test_that() call with braced expression for `code` is matched", { 14 | code_source <- ' 15 | test_that("desc bracket body", { 16 | x <- 1 + 1 17 | expect_equal(x, 2) 18 | }) 19 | ' 20 | captures <- get_captures(code_source, read_fixture("test_that.scm")) 21 | 22 | expect_test_that_captures( 23 | code_source, 24 | "\"desc bracket body\"", 25 | captures 26 | ) 27 | expect_top_level(captures[["node"]][[1]]) 28 | }) 29 | 30 | test_that("test_that(code, desc = 'desc') is matched", { 31 | code_source <- 'test_that(expect_true(TRUE), desc = "desc after code")' 32 | captures <- get_captures(code_source, read_fixture("test_that.scm")) 33 | 34 | expect_test_that_captures( 35 | code_source, 36 | "\"desc after code\"", 37 | captures 38 | ) 39 | expect_top_level(captures[["node"]][[1]]) 40 | }) 41 | 42 | test_that("test_that() with <2 args does not match", { 43 | code_source <- 'test_that("desc only, no code")' 44 | captures <- get_captures(code_source, read_fixture("test_that.scm")) 45 | expect_length(captures$node, 0) 46 | }) 47 | 48 | test_that("test_that() with >2 args does not match", { 49 | code_source <- 'test_that("desc", expect_true(TRUE), other_stuff)' 50 | captures <- get_captures(code_source, read_fixture("test_that.scm")) 51 | expect_length(captures$node, 0) 52 | }) 53 | 54 | test_that("testthat::test_that() is matched", { 55 | code_source <- 'testthat::test_that("with testthat::", expect_true(TRUE))' 56 | captures <- get_captures(code_source, read_fixture("test_that.scm")) 57 | 58 | expect_test_that_captures( 59 | code_source, 60 | "\"with testthat::\"", 61 | captures 62 | ) 63 | 64 | expect_top_level(captures[["node"]][[1]]) 65 | }) 66 | 67 | test_that("OTHERPKG::test_that() does not match", { 68 | code_source <- 'OTHERPKG::test_that("with OTHERPKG::", expect_true(TRUE))' 69 | captures <- get_captures(code_source, read_fixture("test_that.scm")) 70 | expect_length(captures$node, 0) 71 | }) 72 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-stack-crash.R: -------------------------------------------------------------------------------- 1 | test_that("1025 unmatched opening braces", { 2 | # We don't really care about the actual result value, the important thing is that it 3 | # doesn't crash. This is 1 nesting level past the `TREE_SITTER_SERIALIZATION_BUFFER_SIZE`, 4 | # which is a pathological edge case. When this occurs, we give up from the external 5 | # scanner and let the internal scanner either handle it or error. 6 | 7 | skip_if_not_installed("treesitter") 8 | 9 | text <- " 10 | {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ 11 | " 12 | 13 | language <- language() 14 | parser <- treesitter::parser(language) 15 | tree <- treesitter::parser_parse(parser, text) 16 | node <- treesitter::tree_root_node(tree) 17 | 18 | expect_true(TRUE) 19 | }) 20 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-start.R: -------------------------------------------------------------------------------- 1 | test_that("`program` always starts at `(0, 0)`", { 2 | # https://github.com/r-lib/tree-sitter-r/issues/151 3 | # 4 | # We don't have tree-sitter corpus tests for this, as this is very specific 5 | # to node positions, and the tree-sitter test infrastructure doesn't report 6 | # that. 7 | skip_if_not_installed("treesitter") 8 | 9 | language <- language() 10 | parser <- treesitter::parser(language) 11 | 12 | test_program_position <- function(text, end_point, end_byte) { 13 | tree <- treesitter::parser_parse(parser, text) 14 | 15 | # Root node is `program` 16 | node <- treesitter::tree_root_node(tree) 17 | 18 | expect_identical( 19 | treesitter::node_start_point(node), 20 | treesitter::point(0, 0) 21 | ) 22 | expect_identical(treesitter::node_end_point(node), end_point) 23 | 24 | expect_identical(treesitter::node_start_byte(node), 0) 25 | expect_identical(treesitter::node_end_byte(node), end_byte) 26 | } 27 | 28 | # Empty file 29 | test_program_position("", treesitter::point(0, 0), 0) 30 | 31 | # Only whitespace 32 | test_program_position(" ", treesitter::point(0, 2), 2) 33 | 34 | # Only newlines (Unix) 35 | test_program_position("\n\n", treesitter::point(2, 0), 2) 36 | 37 | # Only newlines (Windows) 38 | test_program_position("\r\n\r\n", treesitter::point(2, 0), 4) 39 | 40 | # Leading whitespace 41 | test_program_position(" x", treesitter::point(0, 3), 3) 42 | 43 | # Leading newlines (Unix) 44 | test_program_position("\n\nx", treesitter::point(2, 1), 3) 45 | 46 | # Leading newlines (Windows) 47 | test_program_position("\r\n\r\nx", treesitter::point(2, 1), 5) 48 | 49 | # Leading whitespace before comment 50 | test_program_position(" # hi", treesitter::point(0, 6), 6) 51 | 52 | # Leading newlines before comment (Unix) 53 | test_program_position("\n\n# hi", treesitter::point(2, 4), 6) 54 | 55 | # Leading newlines before comment (Windows) 56 | test_program_position("\r\n\r\n# hi", treesitter::point(2, 4), 8) 57 | }) 58 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-strings.R: -------------------------------------------------------------------------------- 1 | test_that_tree_sitter("references/strings.R") 2 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-unclosed.R: -------------------------------------------------------------------------------- 1 | test_that_tree_sitter("references/unclosed.R") 2 | -------------------------------------------------------------------------------- /bindings/r/tests/testthat/test-unicode.R: -------------------------------------------------------------------------------- 1 | skip_if( 2 | identical(tolower(Sys.info()[["sysname"]]), "windows") && 3 | getRversion() < "4.2.0", 4 | "Windows R <4.1 lacks native UTF-8 support." 5 | ) 6 | 7 | test_that_tree_sitter("references/unicode.R") 8 | -------------------------------------------------------------------------------- /bindings/r/tools/abi.R: -------------------------------------------------------------------------------- 1 | # This file is generated by `tools/abi.R`, do not modify by hand. 2 | 3 | #' tree-sitter ABI version 4 | #' 5 | #' `abi()` returns the ABI version that `parser.c` was generated with. This is 6 | #' used to verify that the grammar is compatible with the tree-sitter C library 7 | #' embedded within the treesitter package. 8 | #' 9 | #' @returns A single integer. 10 | #' 11 | #' @noRd 12 | abi <- function() { 13 | # ABI: LANGUAGE_VERSION 14 | LANGUAGE_VERSIONL 15 | } 16 | -------------------------------------------------------------------------------- /bindings/rust/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let src_dir = std::path::Path::new("src"); 3 | 4 | let mut c_config = cc::Build::new(); 5 | c_config.std("c11").include(src_dir); 6 | 7 | #[cfg(target_env = "msvc")] 8 | c_config.flag("-utf-8"); 9 | 10 | let parser_path = src_dir.join("parser.c"); 11 | c_config.file(&parser_path); 12 | println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap()); 13 | 14 | let scanner_path = src_dir.join("scanner.c"); 15 | c_config.file(&scanner_path); 16 | println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); 17 | 18 | c_config.compile("tree-sitter-r"); 19 | } 20 | -------------------------------------------------------------------------------- /bindings/rust/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides R language support for the [tree-sitter] parsing library. 2 | //! 3 | //! Typically, you will use the [`LANGUAGE`] constant to add this language to a 4 | //! tree-sitter [`Parser`], and then use the parser to parse some code: 5 | //! 6 | //! ``` 7 | //! let code = r#" 8 | //! "#; 9 | //! let mut parser = tree_sitter::Parser::new(); 10 | //! let language = tree_sitter_r::LANGUAGE; 11 | //! parser 12 | //! .set_language(&language.into()) 13 | //! .expect("Error loading R parser"); 14 | //! let tree = parser.parse(code, None).unwrap(); 15 | //! assert!(!tree.root_node().has_error()); 16 | //! ``` 17 | //! 18 | //! [`Parser`]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html 19 | //! [tree-sitter]: https://tree-sitter.github.io/ 20 | 21 | use tree_sitter_language::LanguageFn; 22 | 23 | extern "C" { 24 | fn tree_sitter_r() -> *const (); 25 | } 26 | 27 | /// The tree-sitter [`LanguageFn`] for this grammar. 28 | pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_r) }; 29 | 30 | /// The content of the [`node-types.json`] file for this grammar. 31 | /// 32 | /// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types 33 | pub const NODE_TYPES: &str = include_str!("../../src/node-types.json"); 34 | 35 | pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm"); 36 | // pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm"); 37 | pub const LOCALS_QUERY: &str = include_str!("../../queries/locals.scm"); 38 | pub const TAGS_QUERY: &str = include_str!("../../queries/tags.scm"); 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | #[test] 43 | fn test_can_load_grammar() { 44 | let mut parser = tree_sitter::Parser::new(); 45 | parser 46 | .set_language(&super::LANGUAGE.into()) 47 | .expect("Error loading R parser"); 48 | } 49 | 50 | // See https://github.com/tree-sitter/tree-sitter/issues/2767 51 | #[test] 52 | fn test_tree_cursor() { 53 | let mut parser = tree_sitter::Parser::new(); 54 | parser.set_language(&super::LANGUAGE.into()).unwrap(); 55 | 56 | // This is an `identifier` 57 | let text = "foo"; 58 | 59 | let tree = parser.parse(text, None).unwrap(); 60 | 61 | let mut cursor = tree.walk(); 62 | assert_eq!(cursor.node().kind(), "program"); 63 | 64 | // program -> identifier 65 | assert!(cursor.goto_first_child()); 66 | assert_eq!(cursor.node().kind(), "identifier"); 67 | assert_eq!(cursor.node().utf8_text(text.as_bytes()).unwrap(), "foo"); 68 | 69 | assert!(!cursor.goto_first_child()); 70 | assert_eq!(cursor.node().kind(), "identifier"); 71 | assert_eq!(cursor.node().utf8_text(text.as_bytes()).unwrap(), "foo"); 72 | 73 | assert!(!cursor.goto_last_child()); 74 | assert_eq!(cursor.node().kind(), "identifier"); 75 | assert_eq!(cursor.node().utf8_text(text.as_bytes()).unwrap(), "foo"); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /bindings/swift/TreeSitterR/r.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_SITTER_R_H_ 2 | #define TREE_SITTER_R_H_ 3 | 4 | typedef struct TSLanguage TSLanguage; 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | const TSLanguage *tree_sitter_r(void); 11 | 12 | #ifdef __cplusplus 13 | } 14 | #endif 15 | 16 | #endif // TREE_SITTER_R_H_ 17 | -------------------------------------------------------------------------------- /bindings/swift/TreeSitterRTests/TreeSitterRTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import SwiftTreeSitter 3 | import TreeSitterR 4 | 5 | final class TreeSitterRTests: XCTestCase { 6 | func testCanLoadGrammar() throws { 7 | let parser = Parser() 8 | let language = Language(language: tree_sitter_r()) 9 | XCTAssertNoThrow(try parser.setLanguage(language), 10 | "Error loading R grammar") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/r-lib/tree-sitter-r 2 | 3 | go 1.23 4 | 5 | require github.com/tree-sitter/go-tree-sitter v0.24.0 6 | 7 | require github.com/mattn/go-pointer v0.0.1 // indirect 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@davisvaughan/tree-sitter-r", 3 | "version": "1.2.0", 4 | "description": "R grammar for tree-sitter", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/r-lib/tree-sitter-r.git" 8 | }, 9 | "license": "MIT", 10 | "author": "Davis Vaughan ", 11 | "main": "bindings/node", 12 | "types": "bindings/node", 13 | "keywords": [ 14 | "incremental", 15 | "parsing", 16 | "tree-sitter", 17 | "r" 18 | ], 19 | "files": [ 20 | "grammar.js", 21 | "tree-sitter.json", 22 | "binding.gyp", 23 | "prebuilds/**", 24 | "bindings/node/*", 25 | "queries/*", 26 | "src/**", 27 | "*.wasm" 28 | ], 29 | "dependencies": { 30 | "node-addon-api": "^8.2.1", 31 | "node-gyp-build": "^4.8.2" 32 | }, 33 | "devDependencies": { 34 | "prebuildify": "^6.0.1", 35 | "tree-sitter": "^0.21.1", 36 | "tree-sitter-cli": "^0.24.7" 37 | }, 38 | "peerDependencies": { 39 | "tree-sitter": "^0.21.1" 40 | }, 41 | "peerDependenciesMeta": { 42 | "tree-sitter": { 43 | "optional": true 44 | } 45 | }, 46 | "scripts": { 47 | "install": "node-gyp-build", 48 | "prestart": "tree-sitter build --wasm", 49 | "start": "tree-sitter playground", 50 | "test": "node --test bindings/node/*_test.js" 51 | }, 52 | "bugs": { 53 | "url": "https://github.com/r-lib/tree-sitter-r/issues" 54 | }, 55 | "homepage": "https://github.com/r-lib/tree-sitter-r#readme" 56 | } 57 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "tree-sitter-r" 7 | description = "R grammar for tree-sitter" 8 | version = "1.2.0" 9 | keywords = ["incremental", "parsing", "tree-sitter", "r"] 10 | classifiers = [ 11 | "Intended Audience :: Developers", 12 | "Topic :: Software Development :: Compilers", 13 | "Topic :: Text Processing :: Linguistic", 14 | "Typing :: Typed", 15 | ] 16 | authors = [ 17 | { name = "Kevin Ushey", email = "kevin@posit.co" }, 18 | { name = "Jim Hester", email = "james.f.hester@gmail.com" }, 19 | { name = "Davis Vaughan", email = "davis@posit.co" }, 20 | ] 21 | requires-python = ">=3.9" 22 | license.text = "MIT" 23 | readme = "README.md" 24 | 25 | [project.urls] 26 | Homepage = "https://github.com/r-lib/tree-sitter-r" 27 | 28 | [project.optional-dependencies] 29 | core = ["tree-sitter~=0.23"] 30 | 31 | [tool.cibuildwheel] 32 | build = "cp39-*" 33 | build-frontend = "build" 34 | -------------------------------------------------------------------------------- /queries/highlights.scm: -------------------------------------------------------------------------------- 1 | ; highlights.scm 2 | 3 | ; Literals 4 | 5 | (integer) @number 6 | (float) @number 7 | (complex) @number 8 | 9 | (string) @string 10 | (string (string_content (escape_sequence) @string.escape)) 11 | 12 | ; Comments 13 | 14 | (comment) @comment 15 | 16 | ; Operators 17 | 18 | [ 19 | "?" ":=" "=" "<-" "<<-" "->" "->>" 20 | "~" "|>" "||" "|" "&&" "&" 21 | "<" "<=" ">" ">=" "==" "!=" 22 | "+" "-" "*" "/" "::" ":::" 23 | "**" "^" "$" "@" ":" "!" 24 | "special" 25 | ] @operator 26 | 27 | ; Punctuation 28 | 29 | [ 30 | "(" ")" 31 | "{" "}" 32 | "[" "]" 33 | "[[" "]]" 34 | ] @punctuation.bracket 35 | 36 | (comma) @punctuation.delimiter 37 | 38 | ; Variables 39 | 40 | (identifier) @variable 41 | 42 | ; Functions 43 | 44 | (binary_operator 45 | lhs: (identifier) @function 46 | operator: "<-" 47 | rhs: (function_definition) 48 | ) 49 | 50 | (binary_operator 51 | lhs: (identifier) @function 52 | operator: "=" 53 | rhs: (function_definition) 54 | ) 55 | 56 | ; Calls 57 | 58 | (call function: (identifier) @function) 59 | 60 | ; Parameters 61 | 62 | (parameters (parameter name: (identifier) @variable.parameter)) 63 | (arguments (argument name: (identifier) @variable.parameter)) 64 | 65 | ; Namespace 66 | 67 | (namespace_operator lhs: (identifier) @namespace) 68 | 69 | (call 70 | function: (namespace_operator rhs: (identifier) @function) 71 | ) 72 | 73 | ; Keywords 74 | 75 | (function_definition name: "function" @keyword.function) 76 | (function_definition name: "\\" @operator) 77 | 78 | [ 79 | "in" 80 | (return) 81 | (next) 82 | (break) 83 | ] @keyword 84 | 85 | [ 86 | "if" 87 | "else" 88 | ] @conditional 89 | 90 | [ 91 | "while" 92 | "repeat" 93 | "for" 94 | ] @repeat 95 | 96 | [ 97 | (true) 98 | (false) 99 | ] @boolean 100 | 101 | [ 102 | (null) 103 | (inf) 104 | (nan) 105 | (na) 106 | (dots) 107 | (dot_dot_i) 108 | ] @constant.builtin 109 | 110 | ; Error 111 | 112 | (ERROR) @error 113 | -------------------------------------------------------------------------------- /queries/locals.scm: -------------------------------------------------------------------------------- 1 | ; locals.scm 2 | 3 | (function_definition) @local.scope 4 | 5 | (argument name: (identifier) @local.definition) 6 | (parameter name: (identifier) @local.definition) 7 | 8 | (binary_operator 9 | lhs: (identifier) @local.definition 10 | operator: "<-") 11 | (binary_operator 12 | lhs: (identifier) @local.definition 13 | operator: "=") 14 | (binary_operator 15 | operator: "->" 16 | rhs: (identifier) @local.definition) 17 | 18 | (identifier) @local.reference 19 | -------------------------------------------------------------------------------- /queries/tags.scm: -------------------------------------------------------------------------------- 1 | (binary_operator 2 | lhs: (identifier) @name 3 | operator: "<-" 4 | rhs: (function_definition) 5 | ) @definition.function 6 | 7 | (binary_operator 8 | lhs: (identifier) @name 9 | operator: "=" 10 | rhs: (function_definition) 11 | ) @definition.function 12 | 13 | (binary_operator 14 | lhs: (string) @name 15 | operator: "<-" 16 | rhs: (function_definition) 17 | ) @definition.function 18 | 19 | (binary_operator 20 | lhs: (string) @name 21 | operator: "=" 22 | rhs: (function_definition) 23 | ) @definition.function 24 | 25 | (call 26 | function: (identifier) @name 27 | ) @reference.call 28 | 29 | (call 30 | function: (namespace_operator 31 | rhs: (identifier) @name 32 | ) 33 | ) @reference.call 34 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from os.path import isdir, join 2 | from platform import system 3 | 4 | from setuptools import Extension, find_packages, setup 5 | from setuptools.command.build import build 6 | from wheel.bdist_wheel import bdist_wheel 7 | 8 | 9 | class Build(build): 10 | def run(self): 11 | if isdir("queries"): 12 | dest = join(self.build_lib, "tree_sitter_r", "queries") 13 | self.copy_tree("queries", dest) 14 | super().run() 15 | 16 | 17 | class BdistWheel(bdist_wheel): 18 | def get_tag(self): 19 | python, abi, platform = super().get_tag() 20 | if python.startswith("cp"): 21 | python, abi = "cp39", "abi3" 22 | return python, abi, platform 23 | 24 | 25 | setup( 26 | packages=find_packages("bindings/python"), 27 | package_dir={"": "bindings/python"}, 28 | package_data={ 29 | "tree_sitter_r": ["*.pyi", "py.typed"], 30 | "tree_sitter_r.queries": ["*.scm"], 31 | }, 32 | ext_package="tree_sitter_r", 33 | ext_modules=[ 34 | Extension( 35 | name="_binding", 36 | sources=[ 37 | "bindings/python/tree_sitter_r/binding.c", 38 | "src/parser.c", 39 | "src/scanner.c" 40 | ], 41 | extra_compile_args=[ 42 | "-std=c11", 43 | "-fvisibility=hidden", 44 | ] if system() != "Windows" else [ 45 | "/std:c11", 46 | "/utf-8", 47 | ], 48 | define_macros=[ 49 | ("Py_LIMITED_API", "0x03090000"), 50 | ("PY_SSIZE_T_CLEAN", None), 51 | ("TREE_SITTER_HIDE_SYMBOLS", None), 52 | ], 53 | include_dirs=["src"], 54 | py_limited_api=True, 55 | ) 56 | ], 57 | cmdclass={ 58 | "build": Build, 59 | "bdist_wheel": BdistWheel 60 | }, 61 | zip_safe=False 62 | ) 63 | -------------------------------------------------------------------------------- /src/tree_sitter/alloc.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_SITTER_ALLOC_H_ 2 | #define TREE_SITTER_ALLOC_H_ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | // Allow clients to override allocation functions 13 | #ifdef TREE_SITTER_REUSE_ALLOCATOR 14 | 15 | extern void *(*ts_current_malloc)(size_t size); 16 | extern void *(*ts_current_calloc)(size_t count, size_t size); 17 | extern void *(*ts_current_realloc)(void *ptr, size_t size); 18 | extern void (*ts_current_free)(void *ptr); 19 | 20 | #ifndef ts_malloc 21 | #define ts_malloc ts_current_malloc 22 | #endif 23 | #ifndef ts_calloc 24 | #define ts_calloc ts_current_calloc 25 | #endif 26 | #ifndef ts_realloc 27 | #define ts_realloc ts_current_realloc 28 | #endif 29 | #ifndef ts_free 30 | #define ts_free ts_current_free 31 | #endif 32 | 33 | #else 34 | 35 | #ifndef ts_malloc 36 | #define ts_malloc malloc 37 | #endif 38 | #ifndef ts_calloc 39 | #define ts_calloc calloc 40 | #endif 41 | #ifndef ts_realloc 42 | #define ts_realloc realloc 43 | #endif 44 | #ifndef ts_free 45 | #define ts_free free 46 | #endif 47 | 48 | #endif 49 | 50 | #ifdef __cplusplus 51 | } 52 | #endif 53 | 54 | #endif // TREE_SITTER_ALLOC_H_ 55 | -------------------------------------------------------------------------------- /src/tree_sitter/array.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_SITTER_ARRAY_H_ 2 | #define TREE_SITTER_ARRAY_H_ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "./alloc.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifdef _MSC_VER 17 | #pragma warning(push) 18 | #pragma warning(disable : 4101) 19 | #elif defined(__GNUC__) || defined(__clang__) 20 | #pragma GCC diagnostic push 21 | #pragma GCC diagnostic ignored "-Wunused-variable" 22 | #endif 23 | 24 | #define Array(T) \ 25 | struct { \ 26 | T *contents; \ 27 | uint32_t size; \ 28 | uint32_t capacity; \ 29 | } 30 | 31 | /// Initialize an array. 32 | #define array_init(self) \ 33 | ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL) 34 | 35 | /// Create an empty array. 36 | #define array_new() \ 37 | { NULL, 0, 0 } 38 | 39 | /// Get a pointer to the element at a given `index` in the array. 40 | #define array_get(self, _index) \ 41 | (assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index]) 42 | 43 | /// Get a pointer to the first element in the array. 44 | #define array_front(self) array_get(self, 0) 45 | 46 | /// Get a pointer to the last element in the array. 47 | #define array_back(self) array_get(self, (self)->size - 1) 48 | 49 | /// Clear the array, setting its size to zero. Note that this does not free any 50 | /// memory allocated for the array's contents. 51 | #define array_clear(self) ((self)->size = 0) 52 | 53 | /// Reserve `new_capacity` elements of space in the array. If `new_capacity` is 54 | /// less than the array's current capacity, this function has no effect. 55 | #define array_reserve(self, new_capacity) \ 56 | _array__reserve((Array *)(self), array_elem_size(self), new_capacity) 57 | 58 | /// Free any memory allocated for this array. Note that this does not free any 59 | /// memory allocated for the array's contents. 60 | #define array_delete(self) _array__delete((Array *)(self)) 61 | 62 | /// Push a new `element` onto the end of the array. 63 | #define array_push(self, element) \ 64 | (_array__grow((Array *)(self), 1, array_elem_size(self)), \ 65 | (self)->contents[(self)->size++] = (element)) 66 | 67 | /// Increase the array's size by `count` elements. 68 | /// New elements are zero-initialized. 69 | #define array_grow_by(self, count) \ 70 | do { \ 71 | if ((count) == 0) break; \ 72 | _array__grow((Array *)(self), count, array_elem_size(self)); \ 73 | memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \ 74 | (self)->size += (count); \ 75 | } while (0) 76 | 77 | /// Append all elements from one array to the end of another. 78 | #define array_push_all(self, other) \ 79 | array_extend((self), (other)->size, (other)->contents) 80 | 81 | /// Append `count` elements to the end of the array, reading their values from the 82 | /// `contents` pointer. 83 | #define array_extend(self, count, contents) \ 84 | _array__splice( \ 85 | (Array *)(self), array_elem_size(self), (self)->size, \ 86 | 0, count, contents \ 87 | ) 88 | 89 | /// Remove `old_count` elements from the array starting at the given `index`. At 90 | /// the same index, insert `new_count` new elements, reading their values from the 91 | /// `new_contents` pointer. 92 | #define array_splice(self, _index, old_count, new_count, new_contents) \ 93 | _array__splice( \ 94 | (Array *)(self), array_elem_size(self), _index, \ 95 | old_count, new_count, new_contents \ 96 | ) 97 | 98 | /// Insert one `element` into the array at the given `index`. 99 | #define array_insert(self, _index, element) \ 100 | _array__splice((Array *)(self), array_elem_size(self), _index, 0, 1, &(element)) 101 | 102 | /// Remove one element from the array at the given `index`. 103 | #define array_erase(self, _index) \ 104 | _array__erase((Array *)(self), array_elem_size(self), _index) 105 | 106 | /// Pop the last element off the array, returning the element by value. 107 | #define array_pop(self) ((self)->contents[--(self)->size]) 108 | 109 | /// Assign the contents of one array to another, reallocating if necessary. 110 | #define array_assign(self, other) \ 111 | _array__assign((Array *)(self), (const Array *)(other), array_elem_size(self)) 112 | 113 | /// Swap one array with another 114 | #define array_swap(self, other) \ 115 | _array__swap((Array *)(self), (Array *)(other)) 116 | 117 | /// Get the size of the array contents 118 | #define array_elem_size(self) (sizeof *(self)->contents) 119 | 120 | /// Search a sorted array for a given `needle` value, using the given `compare` 121 | /// callback to determine the order. 122 | /// 123 | /// If an existing element is found to be equal to `needle`, then the `index` 124 | /// out-parameter is set to the existing value's index, and the `exists` 125 | /// out-parameter is set to true. Otherwise, `index` is set to an index where 126 | /// `needle` should be inserted in order to preserve the sorting, and `exists` 127 | /// is set to false. 128 | #define array_search_sorted_with(self, compare, needle, _index, _exists) \ 129 | _array__search_sorted(self, 0, compare, , needle, _index, _exists) 130 | 131 | /// Search a sorted array for a given `needle` value, using integer comparisons 132 | /// of a given struct field (specified with a leading dot) to determine the order. 133 | /// 134 | /// See also `array_search_sorted_with`. 135 | #define array_search_sorted_by(self, field, needle, _index, _exists) \ 136 | _array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists) 137 | 138 | /// Insert a given `value` into a sorted array, using the given `compare` 139 | /// callback to determine the order. 140 | #define array_insert_sorted_with(self, compare, value) \ 141 | do { \ 142 | unsigned _index, _exists; \ 143 | array_search_sorted_with(self, compare, &(value), &_index, &_exists); \ 144 | if (!_exists) array_insert(self, _index, value); \ 145 | } while (0) 146 | 147 | /// Insert a given `value` into a sorted array, using integer comparisons of 148 | /// a given struct field (specified with a leading dot) to determine the order. 149 | /// 150 | /// See also `array_search_sorted_by`. 151 | #define array_insert_sorted_by(self, field, value) \ 152 | do { \ 153 | unsigned _index, _exists; \ 154 | array_search_sorted_by(self, field, (value) field, &_index, &_exists); \ 155 | if (!_exists) array_insert(self, _index, value); \ 156 | } while (0) 157 | 158 | // Private 159 | 160 | typedef Array(void) Array; 161 | 162 | /// This is not what you're looking for, see `array_delete`. 163 | static inline void _array__delete(Array *self) { 164 | if (self->contents) { 165 | ts_free(self->contents); 166 | self->contents = NULL; 167 | self->size = 0; 168 | self->capacity = 0; 169 | } 170 | } 171 | 172 | /// This is not what you're looking for, see `array_erase`. 173 | static inline void _array__erase(Array *self, size_t element_size, 174 | uint32_t index) { 175 | assert(index < self->size); 176 | char *contents = (char *)self->contents; 177 | memmove(contents + index * element_size, contents + (index + 1) * element_size, 178 | (self->size - index - 1) * element_size); 179 | self->size--; 180 | } 181 | 182 | /// This is not what you're looking for, see `array_reserve`. 183 | static inline void _array__reserve(Array *self, size_t element_size, uint32_t new_capacity) { 184 | if (new_capacity > self->capacity) { 185 | if (self->contents) { 186 | self->contents = ts_realloc(self->contents, new_capacity * element_size); 187 | } else { 188 | self->contents = ts_malloc(new_capacity * element_size); 189 | } 190 | self->capacity = new_capacity; 191 | } 192 | } 193 | 194 | /// This is not what you're looking for, see `array_assign`. 195 | static inline void _array__assign(Array *self, const Array *other, size_t element_size) { 196 | _array__reserve(self, element_size, other->size); 197 | self->size = other->size; 198 | memcpy(self->contents, other->contents, self->size * element_size); 199 | } 200 | 201 | /// This is not what you're looking for, see `array_swap`. 202 | static inline void _array__swap(Array *self, Array *other) { 203 | Array swap = *other; 204 | *other = *self; 205 | *self = swap; 206 | } 207 | 208 | /// This is not what you're looking for, see `array_push` or `array_grow_by`. 209 | static inline void _array__grow(Array *self, uint32_t count, size_t element_size) { 210 | uint32_t new_size = self->size + count; 211 | if (new_size > self->capacity) { 212 | uint32_t new_capacity = self->capacity * 2; 213 | if (new_capacity < 8) new_capacity = 8; 214 | if (new_capacity < new_size) new_capacity = new_size; 215 | _array__reserve(self, element_size, new_capacity); 216 | } 217 | } 218 | 219 | /// This is not what you're looking for, see `array_splice`. 220 | static inline void _array__splice(Array *self, size_t element_size, 221 | uint32_t index, uint32_t old_count, 222 | uint32_t new_count, const void *elements) { 223 | uint32_t new_size = self->size + new_count - old_count; 224 | uint32_t old_end = index + old_count; 225 | uint32_t new_end = index + new_count; 226 | assert(old_end <= self->size); 227 | 228 | _array__reserve(self, element_size, new_size); 229 | 230 | char *contents = (char *)self->contents; 231 | if (self->size > old_end) { 232 | memmove( 233 | contents + new_end * element_size, 234 | contents + old_end * element_size, 235 | (self->size - old_end) * element_size 236 | ); 237 | } 238 | if (new_count > 0) { 239 | if (elements) { 240 | memcpy( 241 | (contents + index * element_size), 242 | elements, 243 | new_count * element_size 244 | ); 245 | } else { 246 | memset( 247 | (contents + index * element_size), 248 | 0, 249 | new_count * element_size 250 | ); 251 | } 252 | } 253 | self->size += new_count - old_count; 254 | } 255 | 256 | /// A binary search routine, based on Rust's `std::slice::binary_search_by`. 257 | /// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`. 258 | #define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \ 259 | do { \ 260 | *(_index) = start; \ 261 | *(_exists) = false; \ 262 | uint32_t size = (self)->size - *(_index); \ 263 | if (size == 0) break; \ 264 | int comparison; \ 265 | while (size > 1) { \ 266 | uint32_t half_size = size / 2; \ 267 | uint32_t mid_index = *(_index) + half_size; \ 268 | comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \ 269 | if (comparison <= 0) *(_index) = mid_index; \ 270 | size -= half_size; \ 271 | } \ 272 | comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \ 273 | if (comparison == 0) *(_exists) = true; \ 274 | else if (comparison < 0) *(_index) += 1; \ 275 | } while (0) 276 | 277 | /// Helper macro for the `_sorted_by` routines below. This takes the left (existing) 278 | /// parameter by reference in order to work with the generic sorting function above. 279 | #define _compare_int(a, b) ((int)*(a) - (int)(b)) 280 | 281 | #ifdef _MSC_VER 282 | #pragma warning(pop) 283 | #elif defined(__GNUC__) || defined(__clang__) 284 | #pragma GCC diagnostic pop 285 | #endif 286 | 287 | #ifdef __cplusplus 288 | } 289 | #endif 290 | 291 | #endif // TREE_SITTER_ARRAY_H_ 292 | -------------------------------------------------------------------------------- /src/tree_sitter/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_SITTER_PARSER_H_ 2 | #define TREE_SITTER_PARSER_H_ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define ts_builtin_sym_error ((TSSymbol)-1) 13 | #define ts_builtin_sym_end 0 14 | #define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024 15 | 16 | #ifndef TREE_SITTER_API_H_ 17 | typedef uint16_t TSStateId; 18 | typedef uint16_t TSSymbol; 19 | typedef uint16_t TSFieldId; 20 | typedef struct TSLanguage TSLanguage; 21 | #endif 22 | 23 | typedef struct { 24 | TSFieldId field_id; 25 | uint8_t child_index; 26 | bool inherited; 27 | } TSFieldMapEntry; 28 | 29 | typedef struct { 30 | uint16_t index; 31 | uint16_t length; 32 | } TSFieldMapSlice; 33 | 34 | typedef struct { 35 | bool visible; 36 | bool named; 37 | bool supertype; 38 | } TSSymbolMetadata; 39 | 40 | typedef struct TSLexer TSLexer; 41 | 42 | struct TSLexer { 43 | int32_t lookahead; 44 | TSSymbol result_symbol; 45 | void (*advance)(TSLexer *, bool); 46 | void (*mark_end)(TSLexer *); 47 | uint32_t (*get_column)(TSLexer *); 48 | bool (*is_at_included_range_start)(const TSLexer *); 49 | bool (*eof)(const TSLexer *); 50 | void (*log)(const TSLexer *, const char *, ...); 51 | }; 52 | 53 | typedef enum { 54 | TSParseActionTypeShift, 55 | TSParseActionTypeReduce, 56 | TSParseActionTypeAccept, 57 | TSParseActionTypeRecover, 58 | } TSParseActionType; 59 | 60 | typedef union { 61 | struct { 62 | uint8_t type; 63 | TSStateId state; 64 | bool extra; 65 | bool repetition; 66 | } shift; 67 | struct { 68 | uint8_t type; 69 | uint8_t child_count; 70 | TSSymbol symbol; 71 | int16_t dynamic_precedence; 72 | uint16_t production_id; 73 | } reduce; 74 | uint8_t type; 75 | } TSParseAction; 76 | 77 | typedef struct { 78 | uint16_t lex_state; 79 | uint16_t external_lex_state; 80 | } TSLexMode; 81 | 82 | typedef union { 83 | TSParseAction action; 84 | struct { 85 | uint8_t count; 86 | bool reusable; 87 | } entry; 88 | } TSParseActionEntry; 89 | 90 | typedef struct { 91 | int32_t start; 92 | int32_t end; 93 | } TSCharacterRange; 94 | 95 | struct TSLanguage { 96 | uint32_t version; 97 | uint32_t symbol_count; 98 | uint32_t alias_count; 99 | uint32_t token_count; 100 | uint32_t external_token_count; 101 | uint32_t state_count; 102 | uint32_t large_state_count; 103 | uint32_t production_id_count; 104 | uint32_t field_count; 105 | uint16_t max_alias_sequence_length; 106 | const uint16_t *parse_table; 107 | const uint16_t *small_parse_table; 108 | const uint32_t *small_parse_table_map; 109 | const TSParseActionEntry *parse_actions; 110 | const char * const *symbol_names; 111 | const char * const *field_names; 112 | const TSFieldMapSlice *field_map_slices; 113 | const TSFieldMapEntry *field_map_entries; 114 | const TSSymbolMetadata *symbol_metadata; 115 | const TSSymbol *public_symbol_map; 116 | const uint16_t *alias_map; 117 | const TSSymbol *alias_sequences; 118 | const TSLexMode *lex_modes; 119 | bool (*lex_fn)(TSLexer *, TSStateId); 120 | bool (*keyword_lex_fn)(TSLexer *, TSStateId); 121 | TSSymbol keyword_capture_token; 122 | struct { 123 | const bool *states; 124 | const TSSymbol *symbol_map; 125 | void *(*create)(void); 126 | void (*destroy)(void *); 127 | bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist); 128 | unsigned (*serialize)(void *, char *); 129 | void (*deserialize)(void *, const char *, unsigned); 130 | } external_scanner; 131 | const TSStateId *primary_state_ids; 132 | }; 133 | 134 | static inline bool set_contains(TSCharacterRange *ranges, uint32_t len, int32_t lookahead) { 135 | uint32_t index = 0; 136 | uint32_t size = len - index; 137 | while (size > 1) { 138 | uint32_t half_size = size / 2; 139 | uint32_t mid_index = index + half_size; 140 | TSCharacterRange *range = &ranges[mid_index]; 141 | if (lookahead >= range->start && lookahead <= range->end) { 142 | return true; 143 | } else if (lookahead > range->end) { 144 | index = mid_index; 145 | } 146 | size -= half_size; 147 | } 148 | TSCharacterRange *range = &ranges[index]; 149 | return (lookahead >= range->start && lookahead <= range->end); 150 | } 151 | 152 | /* 153 | * Lexer Macros 154 | */ 155 | 156 | #ifdef _MSC_VER 157 | #define UNUSED __pragma(warning(suppress : 4101)) 158 | #else 159 | #define UNUSED __attribute__((unused)) 160 | #endif 161 | 162 | #define START_LEXER() \ 163 | bool result = false; \ 164 | bool skip = false; \ 165 | UNUSED \ 166 | bool eof = false; \ 167 | int32_t lookahead; \ 168 | goto start; \ 169 | next_state: \ 170 | lexer->advance(lexer, skip); \ 171 | start: \ 172 | skip = false; \ 173 | lookahead = lexer->lookahead; 174 | 175 | #define ADVANCE(state_value) \ 176 | { \ 177 | state = state_value; \ 178 | goto next_state; \ 179 | } 180 | 181 | #define ADVANCE_MAP(...) \ 182 | { \ 183 | static const uint16_t map[] = { __VA_ARGS__ }; \ 184 | for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \ 185 | if (map[i] == lookahead) { \ 186 | state = map[i + 1]; \ 187 | goto next_state; \ 188 | } \ 189 | } \ 190 | } 191 | 192 | #define SKIP(state_value) \ 193 | { \ 194 | skip = true; \ 195 | state = state_value; \ 196 | goto next_state; \ 197 | } 198 | 199 | #define ACCEPT_TOKEN(symbol_value) \ 200 | result = true; \ 201 | lexer->result_symbol = symbol_value; \ 202 | lexer->mark_end(lexer); 203 | 204 | #define END_STATE() return result; 205 | 206 | /* 207 | * Parse Table Macros 208 | */ 209 | 210 | #define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT) 211 | 212 | #define STATE(id) id 213 | 214 | #define ACTIONS(id) id 215 | 216 | #define SHIFT(state_value) \ 217 | {{ \ 218 | .shift = { \ 219 | .type = TSParseActionTypeShift, \ 220 | .state = (state_value) \ 221 | } \ 222 | }} 223 | 224 | #define SHIFT_REPEAT(state_value) \ 225 | {{ \ 226 | .shift = { \ 227 | .type = TSParseActionTypeShift, \ 228 | .state = (state_value), \ 229 | .repetition = true \ 230 | } \ 231 | }} 232 | 233 | #define SHIFT_EXTRA() \ 234 | {{ \ 235 | .shift = { \ 236 | .type = TSParseActionTypeShift, \ 237 | .extra = true \ 238 | } \ 239 | }} 240 | 241 | #define REDUCE(symbol_name, children, precedence, prod_id) \ 242 | {{ \ 243 | .reduce = { \ 244 | .type = TSParseActionTypeReduce, \ 245 | .symbol = symbol_name, \ 246 | .child_count = children, \ 247 | .dynamic_precedence = precedence, \ 248 | .production_id = prod_id \ 249 | }, \ 250 | }} 251 | 252 | #define RECOVER() \ 253 | {{ \ 254 | .type = TSParseActionTypeRecover \ 255 | }} 256 | 257 | #define ACCEPT_INPUT() \ 258 | {{ \ 259 | .type = TSParseActionTypeAccept \ 260 | }} 261 | 262 | #ifdef __cplusplus 263 | } 264 | #endif 265 | 266 | #endif // TREE_SITTER_PARSER_H_ 267 | -------------------------------------------------------------------------------- /test/corpus/expressions-errors.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | Function No Body With Assignment 3 | ================================================================================ 4 | 5 | x <- function(x, y) 6 | 7 | -------------------------------------------------------------------------------- 8 | 9 | (program 10 | (binary_operator 11 | lhs: (identifier) 12 | rhs: (function_definition 13 | parameters: (parameters 14 | parameter: (parameter 15 | name: (identifier)) 16 | (comma) 17 | parameter: (parameter 18 | name: (identifier))) 19 | body: (MISSING identifier)))) 20 | 21 | ================================================================================ 22 | Function No Body Inside Another Function 23 | ================================================================================ 24 | 25 | function(x = function()) {} 26 | 27 | -------------------------------------------------------------------------------- 28 | 29 | (program 30 | (ERROR 31 | (identifier) 32 | (parameters) 33 | (UNEXPECTED ')'))) 34 | 35 | ================================================================================ 36 | Not A Call, Subset, Or Subset2 37 | ================================================================================ 38 | 39 | f 40 | (x) 41 | 42 | foo 43 | [bar] 44 | 45 | foo 46 | [[x]] 47 | 48 | -------------------------------------------------------------------------------- 49 | 50 | (program 51 | (identifier) 52 | (parenthesized_expression 53 | body: (identifier)) 54 | (identifier) 55 | (ERROR 56 | (UNEXPECTED '[')) 57 | (identifier) 58 | (ERROR 59 | (UNEXPECTED ']')) 60 | (identifier) 61 | (ERROR 62 | (UNEXPECTED '[')) 63 | (identifier) 64 | (ERROR 65 | (UNEXPECTED ']')) 66 | ) 67 | 68 | ================================================================================ 69 | Not A Call, Subset, Or Subset2 Due To Sequential Arguments 70 | ================================================================================ 71 | 72 | f(x y) 73 | foo[x y] 74 | foo[[x y]] 75 | 76 | f(x, y z) 77 | foo[x, y z] 78 | foo[[x, y z]] 79 | 80 | f(, x y) 81 | foo[, x y] 82 | foo[[, x y]] 83 | 84 | -------------------------------------------------------------------------------- 85 | 86 | (program 87 | (call 88 | function: (identifier) 89 | arguments: (arguments 90 | (ERROR 91 | (identifier)) 92 | argument: (argument 93 | value: (identifier)))) 94 | (subset 95 | function: (identifier) 96 | arguments: (arguments 97 | (ERROR 98 | (identifier)) 99 | argument: (argument 100 | value: (identifier)))) 101 | (subset2 102 | function: (identifier) 103 | arguments: (arguments 104 | (ERROR 105 | (identifier)) 106 | argument: (argument 107 | value: (identifier)))) 108 | (call 109 | function: (identifier) 110 | arguments: (arguments 111 | argument: (argument 112 | value: (identifier)) 113 | (comma) 114 | (ERROR 115 | (identifier)) 116 | argument: (argument 117 | value: (identifier)))) 118 | (subset 119 | function: (identifier) 120 | arguments: (arguments 121 | argument: (argument 122 | value: (identifier)) 123 | (comma) 124 | (ERROR 125 | (identifier)) 126 | argument: (argument 127 | value: (identifier)))) 128 | (subset2 129 | function: (identifier) 130 | arguments: (arguments 131 | argument: (argument 132 | value: (identifier)) 133 | (comma) 134 | (ERROR 135 | (identifier)) 136 | argument: (argument 137 | value: (identifier)))) 138 | (call 139 | function: (identifier) 140 | arguments: (arguments 141 | (comma) 142 | (ERROR 143 | (identifier)) 144 | argument: (argument 145 | value: (identifier)))) 146 | (subset 147 | function: (identifier) 148 | arguments: (arguments 149 | (comma) 150 | (ERROR 151 | (identifier)) 152 | argument: (argument 153 | value: (identifier)))) 154 | (subset2 155 | function: (identifier) 156 | arguments: (arguments 157 | (comma) 158 | (ERROR 159 | (identifier)) 160 | argument: (argument 161 | value: (identifier))))) 162 | 163 | ================================================================================ 164 | Not A Parenthesized Expression 1 165 | ================================================================================ 166 | 167 | () 168 | 169 | -------------------------------------------------------------------------------- 170 | 171 | (program 172 | (ERROR 173 | (UNEXPECTED ')'))) 174 | 175 | ================================================================================ 176 | Not A Parenthesized Expression 2 177 | ================================================================================ 178 | 179 | ( 180 | 1 181 | 2 182 | ) 183 | 184 | -------------------------------------------------------------------------------- 185 | 186 | (program 187 | (parenthesized_expression 188 | (ERROR) 189 | (float))) 190 | 191 | ================================================================================ 192 | Not A Parenthesized Expression 3 193 | ================================================================================ 194 | 195 | (1; 2) 196 | 197 | -------------------------------------------------------------------------------- 198 | 199 | (program 200 | (parenthesized_expression 201 | (ERROR 202 | (UNEXPECTED ';')) 203 | (float))) 204 | 205 | ================================================================================ 206 | Dollar, At, Namespace, Namespace Internal With `if` RHS 207 | ================================================================================ 208 | # The RHS's of these nodes are restricted to strings and identifiers, and the RHS 209 | # is optional. This ends up trying to match to an `if_statement` node, leaving the RHS 210 | # empty, and then the `if_statement` node errors because it doesn't have a `(`. This 211 | # is actually pretty decent behavior. 212 | foo$if 213 | foo@if 214 | foo::if 215 | foo:::if 216 | 217 | -------------------------------------------------------------------------------- 218 | 219 | (program 220 | (comment) 221 | (comment) 222 | (comment) 223 | (comment) 224 | (extract_operator 225 | (identifier)) 226 | (ERROR) 227 | (extract_operator 228 | (identifier)) 229 | (ERROR) 230 | (namespace_operator 231 | (identifier)) 232 | (ERROR) 233 | (namespace_operator 234 | (identifier)) 235 | (ERROR)) 236 | 237 | ================================================================================ 238 | Dollar, At, Namespace, Namespace Internal With Newline Before Operator 239 | ================================================================================ 240 | 241 | # Newlines are not allowed before the operator, so none of these are valid 242 | x 243 | $y 244 | 245 | x 246 | @y 247 | 248 | x 249 | ::y 250 | 251 | x 252 | :::y 253 | 254 | -------------------------------------------------------------------------------- 255 | 256 | (program 257 | (comment) 258 | (identifier) 259 | (ERROR) 260 | (identifier) 261 | (identifier) 262 | (ERROR) 263 | (identifier) 264 | (identifier) 265 | (ERROR) 266 | (identifier) 267 | (identifier) 268 | (ERROR) 269 | (identifier)) 270 | 271 | ================================================================================ 272 | Closing Brace 273 | ================================================================================ 274 | 275 | } 276 | 277 | -------------------------------------------------------------------------------- 278 | 279 | (program 280 | (ERROR 281 | (UNEXPECTED '}'))) 282 | 283 | ================================================================================ 284 | Closing Parenthesis 285 | ================================================================================ 286 | 287 | ) 288 | 289 | -------------------------------------------------------------------------------- 290 | 291 | (program 292 | (ERROR 293 | (UNEXPECTED ')'))) 294 | 295 | ================================================================================ 296 | Closing Bracket 297 | ================================================================================ 298 | 299 | ] 300 | 301 | -------------------------------------------------------------------------------- 302 | 303 | (program 304 | (ERROR 305 | (UNEXPECTED ']'))) 306 | 307 | ================================================================================ 308 | Opening Brace, Closing Parenthesis 309 | ================================================================================ 310 | 311 | # Parenthesis is "not valid" so it isn't matched by the external scanner 312 | {) 313 | 314 | -------------------------------------------------------------------------------- 315 | 316 | (program 317 | (comment) 318 | (ERROR 319 | (UNEXPECTED ')'))) 320 | 321 | ================================================================================ 322 | Opening Parenthesis, Closing Brace 323 | ================================================================================ 324 | 325 | (} 326 | 327 | -------------------------------------------------------------------------------- 328 | 329 | (program 330 | (ERROR 331 | (UNEXPECTED '}'))) 332 | 333 | ================================================================================ 334 | Opening Parenthesis, Closing Bracket 335 | ================================================================================ 336 | 337 | (] 338 | 339 | -------------------------------------------------------------------------------- 340 | 341 | (program 342 | (ERROR 343 | (UNEXPECTED ']'))) 344 | 345 | ================================================================================ 346 | Opening Bracket2, Unmatched Closing Bracket 347 | ================================================================================ 348 | 349 | x[[2] 350 | 351 | -------------------------------------------------------------------------------- 352 | 353 | (program 354 | (identifier) 355 | (ERROR 356 | (UNEXPECTED ']'))) 357 | 358 | ================================================================================ 359 | Opening Bracket and Bracket2, Unmatched Closing Bracket 360 | ================================================================================ 361 | 362 | x[y[[2] 363 | 364 | -------------------------------------------------------------------------------- 365 | 366 | (program 367 | (identifier) 368 | (ERROR 369 | (identifier) 370 | (UNEXPECTED ']'))) 371 | 372 | ================================================================================ 373 | Opening Bracket2 and Bracket, Matched Closing Bracket 374 | ================================================================================ 375 | 376 | x[[y[2] 377 | 378 | -------------------------------------------------------------------------------- 379 | 380 | (program 381 | (subset2 382 | function: (identifier) 383 | arguments: (arguments 384 | argument: (argument 385 | value: (subset 386 | function: (identifier) 387 | arguments: (arguments 388 | argument: (argument 389 | value: (float))))) 390 | close: (MISSING "]]")))) 391 | 392 | ================================================================================ 393 | Opening Bracket2 and Bracket, Unmatched Closing Bracket 394 | ================================================================================ 395 | 396 | x[[y[2]] 397 | 398 | -------------------------------------------------------------------------------- 399 | 400 | (program 401 | (identifier) 402 | (ERROR 403 | (subset 404 | function: (identifier) 405 | arguments: (arguments 406 | argument: (argument 407 | value: (float)))) 408 | (UNEXPECTED ']'))) 409 | 410 | ================================================================================ 411 | Calls With NA As Argument Name 412 | ================================================================================ 413 | 414 | # Unlike `NULL`, other keywords like `NA` aren't allowed here 415 | fn(NA = ) 416 | 417 | -------------------------------------------------------------------------------- 418 | 419 | (program 420 | (comment) 421 | (identifier) 422 | (ERROR 423 | (na) 424 | (UNEXPECTED ')'))) 425 | 426 | ================================================================================ 427 | Unsupported Subset2 With `]]` Over Multiple Lines (#177) 428 | ================================================================================ 429 | 430 | x[["a"] 431 | ] 432 | 433 | -------------------------------------------------------------------------------- 434 | 435 | (program 436 | (identifier) 437 | (ERROR 438 | (string 439 | (string_content)) 440 | (UNEXPECTED ']'))) 441 | -------------------------------------------------------------------------------- /test/highlight/constant.R: -------------------------------------------------------------------------------- 1 | NULL 2 | # ^ @constant.builtin 3 | Inf 4 | # ^ @constant.builtin 5 | -Inf 6 | # ^ @constant.builtin 7 | NaN 8 | # ^ @constant.builtin 9 | NA 10 | #^ @constant.builtin 11 | NA_real_ 12 | # ^ @constant.builtin 13 | NA_integer_ 14 | # ^ @constant.builtin 15 | NA_character_ 16 | # ^ @constant.builtin 17 | NA_complex_ 18 | # ^ @constant.builtin 19 | ... 20 | # ^ @constant.builtin 21 | ..1 22 | # ^ @constant.builtin 23 | 24 | TRUE 25 | # ^ @boolean 26 | FALSE 27 | # ^ @boolean 28 | -------------------------------------------------------------------------------- /test/highlight/control.R: -------------------------------------------------------------------------------- 1 | for (i in 1:5) { 2 | # ^ repeat 3 | # ^ keyword 4 | return() 5 | # ^ @keyword 6 | 7 | next 8 | # ^ @keyword 9 | 10 | break 11 | # ^ @keyword 12 | } 13 | 14 | while (a > b) { 15 | # ^ repeat 16 | } 17 | 18 | repeat { 19 | # ^ repeat 20 | } 21 | 22 | if (a > b) { 23 | # <- conditional 24 | } else if (b > c) { 25 | # ^ conditional 26 | # ^ conditional 27 | } else { 28 | # ^ conditional 29 | } 30 | -------------------------------------------------------------------------------- /test/highlight/functions.R: -------------------------------------------------------------------------------- 1 | foo <- function() {} 2 | # ^ function 3 | # ^ keyword.function 4 | 5 | foo = function(a, b = 2, d) { 6 | # ^ function 7 | # ^ keyword.function 8 | # ^ variable.parameter 9 | # ^ variable.parameter 10 | # ^ variable.parameter 11 | a + x + d 12 | # ^ variable.parameter 13 | # ^ variable 14 | # ^ variable.parameter 15 | } 16 | 17 | # Note: We align the capture name of function declarations and function calls 18 | # so they can have the same highlight color 19 | foo(a, b, d = 2) 20 | # ^ function 21 | # ^ variable 22 | # ^ variable 23 | # ^ variable.parameter 24 | 25 | \(x) x + y 26 | # <- operator 27 | # ^ variable.parameter 28 | # ^ variable.parameter 29 | # ^ variable 30 | -------------------------------------------------------------------------------- /test/highlight/literals.R: -------------------------------------------------------------------------------- 1 | "x" 2 | # ^ string 3 | 4 | "\n1 + 2\u123 and \U123" 5 | # ^ string.escape 6 | # ^ string.escape 7 | # ^ string.escape 8 | 9 | 1L 10 | # <- number 11 | 12 | 1.5 13 | # ^ number 14 | 15 | 1+3i 16 | # <- number 17 | # ^ number 18 | -------------------------------------------------------------------------------- /test/highlight/namespace.R: -------------------------------------------------------------------------------- 1 | pkg::fn() 2 | # ^ namespace 3 | # ^ function 4 | 5 | pkg:::fn() 6 | # ^ namespace 7 | # ^ function 8 | 9 | pkg::fn 10 | # ^ namespace 11 | # ^ variable 12 | 13 | pkg:::fn 14 | # ^ namespace 15 | # ^ variable 16 | -------------------------------------------------------------------------------- /test/highlight/operators.R: -------------------------------------------------------------------------------- 1 | ?foo 2 | # <- operator 3 | 4 | pkg?foo 5 | # ^ operator 6 | 7 | x := 1 8 | # ^ operator 9 | 10 | x = 1 11 | # ^ operator 12 | 13 | x <- 1 14 | # ^ operator 15 | 16 | x <<- 1 17 | # ^ operator 18 | 19 | 1 -> x 20 | # ^ operator 21 | 22 | 1 ->> x 23 | # ^ operator 24 | 25 | ~ x 26 | # <- operator 27 | 28 | 1 ~ x 29 | # ^ operator 30 | 31 | x |> foo() 32 | # ^ operator 33 | 34 | x || y 35 | # ^ operator 36 | 37 | x | y 38 | # ^ operator 39 | 40 | x && y 41 | # ^ operator 42 | 43 | x & y 44 | # ^ operator 45 | 46 | x < y 47 | # ^ operator 48 | 49 | x <= y 50 | # ^ operator 51 | 52 | x > y 53 | # ^ operator 54 | 55 | x >= y 56 | # ^ operator 57 | 58 | x == y 59 | # ^ operator 60 | 61 | x != y 62 | # ^ operator 63 | 64 | x + y 65 | # ^ operator 66 | 67 | x - y 68 | # ^ operator 69 | 70 | x * y 71 | # ^ operator 72 | 73 | x / y 74 | # ^ operator 75 | 76 | pkg::fn 77 | # ^ operator 78 | 79 | pkg:::fn 80 | # ^ operator 81 | 82 | x ** y 83 | # ^ operator 84 | 85 | x ^ y 86 | # ^ operator 87 | 88 | x$y 89 | #^ operator 90 | 91 | x@y 92 | #^ operator 93 | 94 | x:y 95 | #^ operator 96 | 97 | \(x) x + 1 98 | # <- operator 99 | 100 | !x 101 | # <- operator 102 | -------------------------------------------------------------------------------- /test/highlight/punctuation.R: -------------------------------------------------------------------------------- 1 | { 2 | # <- punctuation.bracket 3 | 1 + 1 4 | } 5 | # <- punctuation.bracket 6 | 7 | ( 8 | # <- punctuation.bracket 9 | 1 + 1 10 | ) 11 | # <- punctuation.bracket 12 | 13 | a[b] 14 | #^ punctuation.bracket 15 | # ^ punctuation.bracket 16 | 17 | # No good way to prove that `[[` is one token from what I can tell 18 | a[[b]] 19 | #^ punctuation.bracket 20 | # ^ punctuation.bracket 21 | # ^ punctuation.bracket 22 | # ^ punctuation.bracket 23 | 24 | fn() 25 | # ^ punctuation.bracket 26 | # ^ punctuation.bracket 27 | 28 | fn(a, b) 29 | # ^ punctuation.delimiter 30 | -------------------------------------------------------------------------------- /test/highlight/variables.R: -------------------------------------------------------------------------------- 1 | x + y 2 | # <- variable 3 | # ^ variable 4 | 5 | x <- 1 6 | # <- variable 7 | 8 | # Not a variable 9 | foo <- function() {} 10 | # <- function 11 | 12 | # Not a variable 13 | foo() 14 | # <- function 15 | -------------------------------------------------------------------------------- /test/tags/calls.R: -------------------------------------------------------------------------------- 1 | match(a, b) 2 | # ^ reference.call 3 | 4 | fn <- function() { 5 | foo() 6 | # ^ reference.call 7 | 8 | bar() + baz() 9 | # ^ reference.call 10 | # ^ reference.call 11 | } 12 | 13 | pkg::exported(mtcars, x = 1) 14 | # ^ reference.call 15 | 16 | pkg:::internal(mtcars, x = 1) 17 | # ^ reference.call 18 | -------------------------------------------------------------------------------- /test/tags/function-definitions.R: -------------------------------------------------------------------------------- 1 | fn <- function() { 2 | # <- definition.function 3 | } 4 | 5 | fn = function() { 6 | # <- definition.function 7 | } 8 | 9 | fn <- function(a, b) { 10 | # <- definition.function 11 | 12 | bar <- function() {} 13 | # ^ definition.function 14 | } 15 | 16 | "fn" <- function() { 17 | # <- definition.function 18 | } 19 | 20 | "fn" = function() { 21 | # <- definition.function 22 | } 23 | 24 | 'fn' <- function() { 25 | # <- definition.function 26 | } 27 | 28 | 'fn' = function() { 29 | # <- definition.function 30 | } 31 | 32 | `fn` <- function() { 33 | # <- definition.function 34 | } 35 | 36 | `fn` = function() { 37 | # <- definition.function 38 | } 39 | -------------------------------------------------------------------------------- /tree-sitter.json: -------------------------------------------------------------------------------- 1 | { 2 | "grammars": [ 3 | { 4 | "name": "r", 5 | "camelcase": "R", 6 | "scope": "source.R", 7 | "path": ".", 8 | "file-types": [ 9 | "R", 10 | "r" 11 | ], 12 | "highlights": "queries/highlights.scm", 13 | "tags": "queries/tags.scm", 14 | "injection-regex": "^[Rr]$", 15 | "first-line-regex": "#!.*\\bRscript$" 16 | } 17 | ], 18 | "metadata": { 19 | "version": "1.2.0", 20 | "license": "MIT", 21 | "description": "R grammar for tree-sitter", 22 | "authors": [ 23 | { 24 | "name": "Davis Vaughan", 25 | "email": "davis@posit.co" 26 | }, 27 | { 28 | "name": "Kevin Ushey", 29 | "email": "kevin@posit.co" 30 | }, 31 | { 32 | "name": "Jim Hester", 33 | "email": "james.f.hester@gmail.com" 34 | } 35 | ], 36 | "links": { 37 | "repository": "https://github.com/r-lib/tree-sitter-r" 38 | } 39 | }, 40 | "bindings": { 41 | "c": true, 42 | "go": true, 43 | "node": true, 44 | "python": true, 45 | "rust": true, 46 | "swift": true 47 | } 48 | } 49 | --------------------------------------------------------------------------------