diff --git a/.github/workflows/tree-sitter.yml b/.github/workflows/tree-sitter.yml
new file mode 100644
index 0000000..2541db0
--- /dev/null
+++ b/.github/workflows/tree-sitter.yml
@@ -0,0 +1,45 @@
+name: Tree-sitter Parser
+
+on:
+ pull_request:
+ paths:
+ - ".github/workflows/tree-sitter.yml"
+ - "examples/**/*.bpp"
+ - "makefile"
+ - "test-suite/**/*.bpp"
+ - "tree-sitter-bashpp/**"
+ - "wiki/_includes/code/snippets/**/*.bpp"
+ push:
+ branches: ["main"]
+ paths:
+ - ".github/workflows/tree-sitter.yml"
+ - "examples/**/*.bpp"
+ - "makefile"
+ - "test-suite/**/*.bpp"
+ - "tree-sitter-bashpp/**"
+ - "wiki/_includes/code/snippets/**/*.bpp"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: "22"
+ cache: npm
+ cache-dependency-path: tree-sitter-bashpp/package-lock.json
+
+ - name: Install dependencies
+ working-directory: tree-sitter-bashpp
+ run: npm ci
+
+ - name: Test parser
+ run: make test-tree-sitter
diff --git a/.gitignore b/.gitignore
index 6d3e757..207d809 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,9 @@ debian/*.7
vscode/node_modules
vscode/*.vsix
vscode/dist
+tree-sitter-bashpp/node_modules
+tree-sitter-bashpp/.cache
+tree-sitter-bashpp/src/grammar.json
+tree-sitter-bashpp/src/node-types.json
+tree-sitter-bashpp/src/parser.c
+tree-sitter-bashpp/src/tree_sitter
diff --git a/README.md b/README.md
index 27fa8ce..6bc47a9 100644
--- a/README.md
+++ b/README.md
@@ -70,6 +70,7 @@ Language server-specific prerequisites:
- `nlohmann-json3-dev`
Optional:
+ - `nodejs` and `npm` for developing the Tree-sitter parser
- `pandoc` and `perl` for building the documentation
- `debhelper` for building the Debian package and keeping version numbers up-to-date via `dpkg-parsechangelog`
@@ -86,6 +87,8 @@ $ sudo apt install build-essential flex bison libutfcpp-dev pandoc perl debhelpe
$ make # Build the Bash++ compiler and language server, bin/bpp and bin/bpp-lsp
$ make manpages # Build the manpages, which can then be found under debian/
$ make test # Run the test suite to verify the compiler works correctly
+$ make tree-sitter # Generate the optional Tree-sitter parser
+$ make test-tree-sitter # Test the parser against the repository's Bash++ sources
```
## Using the compiler
diff --git a/makefile b/makefile
index 41b8c49..a189bce 100644
--- a/makefile
+++ b/makefile
@@ -17,13 +17,22 @@ test:
vscode:
@cd vscode && $(MAKE) --no-print-directory
+tree-sitter:
+ @$(MAKE) -C tree-sitter-bashpp --no-print-directory generate
+
+test-tree-sitter:
+ @$(MAKE) -C tree-sitter-bashpp --no-print-directory test
+
clean-vscode:
@cd vscode && $(MAKE) --no-print-directory clean
@echo "Cleaned up VSCode extension files."
-clean: clean-flexbison clean-lsp clean-meta clean-objects clean-bin clean-std clean-manpages clean-technical-docs clean-vscode
+clean-tree-sitter:
+ @$(MAKE) -C tree-sitter-bashpp --no-print-directory clean
+
+clean: clean-flexbison clean-lsp clean-meta clean-objects clean-bin clean-std clean-manpages clean-technical-docs clean-vscode clean-tree-sitter
-.PHONY: all test vscode clean-vscode
+.PHONY: all test vscode tree-sitter test-tree-sitter clean-vscode clean-tree-sitter
ifeq ($(filter clean%,$(MAKECMDGOALS)),)
-include $(shell find bin -name '*.d' 2>/dev/null)
diff --git a/tree-sitter-bashpp/LICENSE b/tree-sitter-bashpp/LICENSE
new file mode 100644
index 0000000..bc02957
--- /dev/null
+++ b/tree-sitter-bashpp/LICENSE
@@ -0,0 +1,15 @@
+Copyright (C) 2026 Bash++ contributors
+
+This file is part of Bash++.
+
+Bash++ is free software: you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+Bash++ is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+Bash++. If not, see .
diff --git a/tree-sitter-bashpp/Makefile b/tree-sitter-bashpp/Makefile
new file mode 100644
index 0000000..160977b
--- /dev/null
+++ b/tree-sitter-bashpp/Makefile
@@ -0,0 +1,20 @@
+.PHONY: all dependencies generate test clean
+
+all: generate
+
+dependencies: node_modules/.package-lock.json
+
+node_modules/.package-lock.json: package-lock.json
+ npm ci
+
+generate: dependencies
+ npm run generate
+
+test: dependencies
+ npm run generate
+ npm run lint
+ npm test
+ npm run test:repository
+
+clean:
+ rm -rf node_modules .cache src/grammar.json src/node-types.json src/parser.c src/tree_sitter
diff --git a/tree-sitter-bashpp/README.md b/tree-sitter-bashpp/README.md
new file mode 100644
index 0000000..aafd197
--- /dev/null
+++ b/tree-sitter-bashpp/README.md
@@ -0,0 +1,53 @@
+# Tree-sitter Bash++
+
+This directory contains the Tree-sitter parser for Bash++. It extends the
+upstream [`tree-sitter-bash`](https://github.com/tree-sitter/tree-sitter-bash)
+0.25.1 grammar so that Bash syntax remains owned and tested by the Bash parser.
+The parser is kept in this repository because the compiler sources, examples,
+and documentation snippets are its compatibility suite.
+
+## Development
+
+From the repository root, install the locked dependencies and generate the
+tracked parser sources:
+
+```sh
+make tree-sitter
+```
+
+Run generation, linting, corpus tests, and repository-wide parsing:
+
+```sh
+make test-tree-sitter
+```
+
+Generated parser sources under `src/` are build artifacts and are not committed.
+Run `make tree-sitter` before compiling the parser or packaging an editor
+integration.
+
+The corpus is grouped by Bash compatibility, declarations, expressions,
+Bash interactions, and error recovery. Repository parsing covers every `.bpp`
+file under `examples/`, `test-suite/`, and `wiki/_includes/code/snippets/`.
+`parser-errors-1.bpp` and `parser-errors-2.bpp` are intentionally malformed and
+are asserted separately as recovery cases.
+
+## Grammar Interface
+
+Named nodes and fields in `grammar.js` are the parser's public interface.
+They cover classes and members, object and pointer declarations, references,
+assignments, allocation and casts, supershells, and Bash++ interpolation in
+Bash constructs. Future highlighting queries and editor integrations should
+consume these nodes rather than infer structure from source text.
+
+The external scanner is inherited from `tree-sitter-bash` and adapted only for
+Bash++ tokens that require lookahead, such as declaration types, assignment
+references, reference boundaries, and heredoc interpolation.
+
+## Scope and Licensing
+
+This milestone contains only the parser. It intentionally excludes highlighting
+queries, editor configuration, language bindings, and language-server support.
+
+New parser work is licensed under GPL-3.0-or-later. Adapted
+`tree-sitter-bash` material retains its MIT notice under
+`THIRD_PARTY_LICENSES/`.
diff --git a/tree-sitter-bashpp/THIRD_PARTY_LICENSES/tree-sitter-bash-MIT.txt b/tree-sitter-bashpp/THIRD_PARTY_LICENSES/tree-sitter-bash-MIT.txt
new file mode 100644
index 0000000..408b69a
--- /dev/null
+++ b/tree-sitter-bashpp/THIRD_PARTY_LICENSES/tree-sitter-bash-MIT.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2017 Max Brunsfeld
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/tree-sitter-bashpp/eslint.config.mjs b/tree-sitter-bashpp/eslint.config.mjs
new file mode 100644
index 0000000..a961a94
--- /dev/null
+++ b/tree-sitter-bashpp/eslint.config.mjs
@@ -0,0 +1,5 @@
+import treeSitter from 'eslint-config-treesitter';
+
+export default [
+ ...treeSitter,
+];
diff --git a/tree-sitter-bashpp/grammar.js b/tree-sitter-bashpp/grammar.js
new file mode 100644
index 0000000..5f76139
--- /dev/null
+++ b/tree-sitter-bashpp/grammar.js
@@ -0,0 +1,500 @@
+/**
+ * @file Bash++ grammar for Tree-sitter
+ * @license GPL-3.0-or-later
+ */
+
+const Bash = require('tree-sitter-bash/grammar');
+
+const BASH_SPECIAL_CHARACTERS = [
+ '\'',
+ '"',
+ '<',
+ '>',
+ '{',
+ '}',
+ '\\[',
+ '\\]',
+ '(',
+ ')',
+ '`',
+ '$',
+ '=',
+ '|',
+ '&',
+ ';',
+ '\\',
+ '\\s',
+ '@',
+];
+
+module.exports = grammar(Bash, {
+ name: 'bashpp',
+
+ externals: ($, previous) => previous.concat([
+ $._bashpp_object_type,
+ $._bashpp_pointer_type,
+ $._bashpp_assignment_reference,
+ $._bashpp_assignment_operator,
+ $._bashpp_reference_end,
+ ]),
+
+ rules: {
+ _statement_not_subshell: ($, previous) => choice(
+ $.variable_assignment,
+ $.class_definition,
+ $.data_member_declaration,
+ $.method_definition,
+ $.constructor_definition,
+ $.destructor_definition,
+ $.include_statement,
+ $.object_declaration,
+ $.pointer_declaration,
+ $.object_assignment,
+ $.delete_statement,
+ previous,
+ ),
+
+ _statement_not_pipeline: ($, previous) => choice(
+ $.variable_assignment,
+ $.class_definition,
+ $.data_member_declaration,
+ $.method_definition,
+ $.constructor_definition,
+ $.destructor_definition,
+ $.include_statement,
+ $.object_declaration,
+ $.pointer_declaration,
+ $.object_assignment,
+ $.delete_statement,
+ previous,
+ ),
+
+ variable_assignment: $ => seq(
+ field('name', choice(
+ $.variable_name,
+ $.subscript,
+ )),
+ field('operator', choice('=', '+=')),
+ field('value', choice(
+ $._bashpp_value,
+ $._empty_value,
+ alias($._comment_word, $.word),
+ )),
+ ),
+
+ subscript: $ => seq(
+ field('name', $.variable_name),
+ '[',
+ field('index', choice(
+ alias('@', $.word),
+ $._literal,
+ $.binary_expression,
+ $.unary_expression,
+ $.compound_statement,
+ $.subshell,
+ )),
+ optional($._concat),
+ ']',
+ optional($._concat),
+ ),
+
+ command: ($, previous) => choice(
+ prec.dynamic(10, seq(
+ field('name', alias(
+ $._bashpp_braced_command_assignment,
+ $.command_name,
+ )),
+ )),
+ previous,
+ ),
+
+ command_name: ($, previous) => choice(
+ $.object_reference,
+ $.self_reference,
+ $.braced_reference,
+ $.pointer_dereference,
+ $.supershell,
+ previous,
+ ),
+
+ class_definition: $ => prec.right(3, seq(
+ bashppKeyword('@class'),
+ field('name', alias($._bashpp_identifier, $.type_identifier)),
+ optional(seq(
+ ':',
+ field('parent', alias($._bashpp_identifier, $.type_identifier)),
+ )),
+ field('body', $.compound_statement),
+ )),
+
+ data_member_declaration: $ => prec.right(4, seq(
+ field('modifier', $.access_modifier),
+ choice(
+ seq(
+ field('name', alias($._bashpp_identifier, $.member_name)),
+ optional(field('value', $.default_value)),
+ ),
+ field('declaration', choice(
+ $.object_declaration,
+ $.pointer_declaration,
+ )),
+ ),
+ )),
+
+ method_definition: $ => prec.right(4, seq(
+ optional(field('virtual', $.virtual_modifier)),
+ field('modifier', $.access_modifier),
+ bashppKeyword('@method'),
+ field('name', alias($._bashpp_identifier, $.method_name)),
+ repeat(field('parameter', $.method_parameter)),
+ field('body', $.compound_statement),
+ )),
+
+ method_parameter: $ => choice(
+ field('name', alias($._bashpp_identifier, $.parameter_name)),
+ seq(
+ field('type', choice(
+ alias($._bashpp_object_type, $.type_identifier),
+ alias($._bashpp_pointer_type, $.type_identifier),
+ )),
+ field('name', alias($._bashpp_identifier, $.parameter_name)),
+ ),
+ ),
+
+ constructor_definition: $ => prec.right(4, seq(
+ bashppKeyword('@constructor'),
+ field('body', $.compound_statement),
+ )),
+
+ destructor_definition: $ => prec.right(4, seq(
+ bashppKeyword('@destructor'),
+ field('body', $.compound_statement),
+ )),
+
+ include_statement: $ => prec.right(4, seq(
+ field('keyword', choice(
+ bashppKeyword('@include'),
+ bashppKeyword('@include_once'),
+ )),
+ optional(field('type', $.include_type)),
+ field('path', $.include_path),
+ optional(seq(
+ 'as',
+ field('alias', $.include_path),
+ )),
+ )),
+
+ include_type: _ => choice('dynamic', 'static'),
+
+ include_path: _ => token(choice(
+ /"([^"\\\r\n]|\\.)+"/,
+ /<[^>\r\n]+>/,
+ )),
+
+ object_declaration: $ => prec.right(3, seq(
+ field('type', alias($._bashpp_object_type, $.type_identifier)),
+ field('name', alias($._bashpp_identifier, $.object_name)),
+ optional(field('value', $.default_value)),
+ )),
+
+ pointer_declaration: $ => prec.right(3, seq(
+ field('type', alias($._bashpp_pointer_type, $.type_identifier)),
+ field('name', alias($._bashpp_identifier, $.object_name)),
+ optional(field('value', $.default_value)),
+ )),
+
+ default_value: $ => seq(
+ field('operator', $.assignment_operator),
+ field('value', $._bashpp_value),
+ ),
+
+ assignment_operator: $ => $._bashpp_assignment_operator,
+
+ access_modifier: _ => choice(
+ bashppKeyword('@public'),
+ bashppKeyword('@private'),
+ bashppKeyword('@protected'),
+ ),
+
+ virtual_modifier: _ => bashppKeyword('@virtual'),
+
+ object_assignment: $ => prec.right(10, seq(
+ field('left', $.assignment_reference),
+ optional(field('index', $.reference_index)),
+ field('operator', $.assignment_operator),
+ field('right', $._bashpp_value),
+ )),
+
+ assignment_reference: $ => field(
+ 'name',
+ alias($._bashpp_assignment_reference, $.reference_name),
+ ),
+
+ object_reference: $ => prec.dynamic(10, seq(
+ field('name', alias($._bashpp_reference_name, $.reference_name)),
+ optional(field('index', $.reference_index)),
+ $._bashpp_reference_end,
+ )),
+
+ self_reference: $ => prec.dynamic(10, seq(
+ field('name', choice(
+ alias($._bashpp_this_reference, $.self),
+ alias($._bashpp_super_reference, $.super),
+ )),
+ optional(field('index', $.reference_index)),
+ $._bashpp_reference_end,
+ )),
+
+ braced_reference: $ => prec.dynamic(10, seq(
+ bashppKeyword('@{'),
+ field('name', alias($._bashpp_braced_name, $.reference_name)),
+ optional(field('index', $.reference_index)),
+ '}',
+ )),
+
+ reference_index: $ => seq(
+ '[',
+ field('value', choice(
+ '@',
+ $._expression,
+ )),
+ ']',
+ ),
+
+ address_expression: $ => prec.dynamic(10, seq(
+ field('name', alias($._bashpp_address_name, $.reference_name)),
+ optional(field('index', $.reference_index)),
+ $._bashpp_reference_end,
+ )),
+
+ pointer_dereference: $ => prec.dynamic(10, seq(
+ field('name', alias(
+ $._bashpp_dereference_name,
+ $.reference_name,
+ )),
+ optional(field('index', $.reference_index)),
+ $._bashpp_reference_end,
+ )),
+
+ new_expression: $ => prec.right(4, seq(
+ bashppKeyword('@new'),
+ field('type', alias($._bashpp_identifier, $.type_identifier)),
+ )),
+
+ delete_statement: $ => prec.right(4, seq(
+ bashppKeyword('@delete'),
+ field('argument', choice(
+ $.object_reference,
+ $.self_reference,
+ )),
+ )),
+
+ nullptr_literal: _ => bashppKeyword('@nullptr'),
+
+ typeof_expression: $ => prec.right(4, seq(
+ bashppKeyword('@typeof'),
+ field('argument', $._bashpp_value),
+ )),
+
+ dynamic_cast_expression: $ => prec.right(4, seq(
+ bashppKeyword('@dynamic_cast'),
+ '<',
+ field('target', $.dynamic_cast_target),
+ '>',
+ field('value', $._bashpp_value),
+ )),
+
+ dynamic_cast_target: $ => choice(
+ alias($._bashpp_cast_type, $.type_identifier),
+ $.simple_expansion,
+ $.expansion,
+ $.object_reference,
+ $.self_reference,
+ $.braced_reference,
+ ),
+
+ supershell: $ => prec.right(4, seq(
+ bashppKeyword('@('),
+ optional($._statements),
+ ')',
+ )),
+
+ _primary_expression: ($, previous) => choice(
+ previous,
+ $.object_reference,
+ $.self_reference,
+ $.braced_reference,
+ $.address_expression,
+ $.pointer_dereference,
+ $.new_expression,
+ $.nullptr_literal,
+ $.typeof_expression,
+ $.dynamic_cast_expression,
+ $.supershell,
+ ),
+
+ _bashpp_braced_command_assignment: $ => prec.dynamic(10, seq(
+ $.braced_reference,
+ $.assignment_operator,
+ $._primary_expression,
+ )),
+
+ _c_expression_not_assignment: ($, previous) => choice(
+ previous,
+ $.object_reference,
+ $.self_reference,
+ $.braced_reference,
+ $.pointer_dereference,
+ $.nullptr_literal,
+ $.typeof_expression,
+ $.supershell,
+ ),
+
+ _arithmetic_literal: ($, previous) => choice(
+ previous,
+ $.object_reference,
+ $.self_reference,
+ $.braced_reference,
+ $.pointer_dereference,
+ $.nullptr_literal,
+ $.typeof_expression,
+ $.supershell,
+ ),
+
+ string: $ => seq(
+ '"',
+ repeat(seq(
+ choice(
+ seq(optional('$'), $.string_content),
+ $.expansion,
+ $.simple_expansion,
+ $.command_substitution,
+ $.arithmetic_expansion,
+ $._bashpp_string_interpolation,
+ ),
+ optional($._concat),
+ )),
+ optional('$'),
+ '"',
+ ),
+
+ string_content: _ => token(prec(-1, /([^"`$@&*\\\r\n]|\\(.|\r?\n))+/)),
+
+ _bashpp_string_interpolation: $ => choice(
+ $.object_reference,
+ $.self_reference,
+ $.braced_reference,
+ $.address_expression,
+ $.pointer_dereference,
+ $.supershell,
+ alias(/[&*]/, $.string_content),
+ ),
+
+ heredoc_body: $ => seq(
+ $._heredoc_body_beginning,
+ repeat(choice(
+ $.expansion,
+ $.simple_expansion,
+ $.command_substitution,
+ $.object_reference,
+ $.self_reference,
+ $.braced_reference,
+ $.address_expression,
+ $.pointer_dereference,
+ $.supershell,
+ $.heredoc_content,
+ )),
+ ),
+
+ _bashpp_value: $ => choice(
+ $._literal,
+ $.array,
+ $.new_expression,
+ $.nullptr_literal,
+ $.typeof_expression,
+ $.dynamic_cast_expression,
+ ),
+
+ word: _ => token(prec(-1, seq(
+ choice(
+ noneOf('#', ...BASH_SPECIAL_CHARACTERS),
+ seq('\\', noneOf('\\s')),
+ ),
+ repeat(choice(
+ noneOf(...BASH_SPECIAL_CHARACTERS),
+ seq('\\', noneOf('\\s')),
+ '\\ ',
+ )),
+ ))),
+
+ _comment_word: _ => token(prec(-8, seq(
+ choice(
+ noneOf(...BASH_SPECIAL_CHARACTERS),
+ seq('\\', noneOf('\\s')),
+ ),
+ repeat(choice(
+ noneOf(...BASH_SPECIAL_CHARACTERS),
+ seq('\\', noneOf('\\s')),
+ '\\ ',
+ )),
+ ))),
+
+ _bashpp_identifier: _ => /[a-zA-Z_][a-zA-Z0-9_]*/,
+
+ _bashpp_cast_type: _ => /[a-zA-Z_][a-zA-Z0-9_]*\*?/,
+
+ _bashpp_reference_name: _ => token(prec(
+ 3,
+ /@[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*/,
+ )),
+
+ _bashpp_this_reference: _ => token(prec(
+ 5,
+ /@this(\.[a-zA-Z_][a-zA-Z0-9_]*)*/,
+ )),
+
+ _bashpp_super_reference: _ => token(prec(
+ 5,
+ /@super(\.[a-zA-Z_][a-zA-Z0-9_]*)*/,
+ )),
+
+ _bashpp_braced_name: _ => token(prec(
+ 2,
+ /#?[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*/,
+ )),
+
+ _bashpp_address_name: _ => token(prec(
+ 3,
+ /&@[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*/,
+ )),
+
+ _bashpp_dereference_name: _ => token(prec(
+ 3,
+ /\*@[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*/,
+ )),
+
+ },
+});
+
+/**
+ * Gives reserved Bash++ words priority over the general `@Type` token.
+ *
+ * @param {string} value
+ * @returns {TokenRule}
+ */
+function bashppKeyword(value) {
+ return token(prec(5, value));
+}
+
+/**
+ * Returns a regular expression that excludes the provided characters.
+ *
+ * @param {...string} characters
+ * @returns {RegExp}
+ */
+function noneOf(...characters) {
+ const negatedCharacters = characters
+ .map(character => character === '\\' ? '\\\\' : character)
+ .join('');
+ return new RegExp('[^' + negatedCharacters + ']');
+}
diff --git a/tree-sitter-bashpp/package-lock.json b/tree-sitter-bashpp/package-lock.json
new file mode 100644
index 0000000..57f0144
--- /dev/null
+++ b/tree-sitter-bashpp/package-lock.json
@@ -0,0 +1,1311 @@
+{
+ "name": "tree-sitter-bashpp",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "tree-sitter-bashpp",
+ "version": "0.1.0",
+ "license": "GPL-3.0-or-later",
+ "dependencies": {
+ "tree-sitter-bash": "0.25.1"
+ },
+ "devDependencies": {
+ "eslint": "9.39.1",
+ "eslint-config-treesitter": "1.0.2",
+ "tree-sitter-cli": "0.25.10"
+ }
+ },
+ "node_modules/@es-joy/jsdoccomment": {
+ "version": "0.50.2",
+ "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.50.2.tgz",
+ "integrity": "sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.6",
+ "@typescript-eslint/types": "^8.11.0",
+ "comment-parser": "1.4.1",
+ "esquery": "^1.6.0",
+ "jsdoc-type-pratt-parser": "~4.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
+ "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.5"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
+ "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.14.0",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.5",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.39.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
+ "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
+ "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/types": "^0.15.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
+ "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.2",
+ "@humanfs/types": "^0.15.0",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/types": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
+ "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.61.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.0.tgz",
+ "integrity": "sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.17.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
+ "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/are-docs-informative": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz",
+ "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
+ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/comment-parser": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz",
+ "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12.0.0"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.39.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
+ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.1",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.39.1",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-treesitter": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/eslint-config-treesitter/-/eslint-config-treesitter-1.0.2.tgz",
+ "integrity": "sha512-OkzjA0oaNgYUFkGmo9T2cvRE7cxzh1dgSt0laO8Hdcypp9di8lebldoPivALXFusRb7s54J5exIw1w7l+g85Rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-plugin-jsdoc": "^50.2.4"
+ },
+ "peerDependencies": {
+ "eslint": ">= 9"
+ }
+ },
+ "node_modules/eslint-plugin-jsdoc": {
+ "version": "50.8.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.8.0.tgz",
+ "integrity": "sha512-UyGb5755LMFWPrZTEqqvTJ3urLz1iqj+bYOHFNag+sw3NvaMWP9K2z+uIn37XfNALmQLQyrBlJ5mkiVPL7ADEg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@es-joy/jsdoccomment": "~0.50.2",
+ "are-docs-informative": "^0.0.2",
+ "comment-parser": "1.4.1",
+ "debug": "^4.4.1",
+ "escape-string-regexp": "^4.0.0",
+ "espree": "^10.3.0",
+ "esquery": "^1.6.0",
+ "parse-imports-exports": "^0.2.4",
+ "semver": "^7.7.2",
+ "spdx-expression-parse": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
+ "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsdoc-type-pratt-parser": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz",
+ "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-addon-api": {
+ "version": "8.8.0",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.8.0.tgz",
+ "integrity": "sha512-c5Ko1fZJIJmzhFIkhRN76WTq+fC6tWnGy9CXA0fA+XygsWZmEwG8vmbkNqxMyoaa0Tin4djul49NzdVcJJcjeA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^18 || ^20 || >= 21"
+ }
+ },
+ "node_modules/node-gyp-build": {
+ "version": "4.8.4",
+ "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
+ "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
+ "license": "MIT",
+ "bin": {
+ "node-gyp-build": "bin.js",
+ "node-gyp-build-optional": "optional.js",
+ "node-gyp-build-test": "build-test.js"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-imports-exports": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz",
+ "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parse-statements": "1.0.11"
+ }
+ },
+ "node_modules/parse-statements": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz",
+ "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz",
+ "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/spdx-exceptions": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
+ "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
+ "dev": true,
+ "license": "CC-BY-3.0"
+ },
+ "node_modules/spdx-expression-parse": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz",
+ "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-license-ids": {
+ "version": "3.0.23",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz",
+ "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tree-sitter-bash": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/tree-sitter-bash/-/tree-sitter-bash-0.25.1.tgz",
+ "integrity": "sha512-7hMytuYIMoXOq24yRulgIxthE9YmggZIOHCyPTTuJcu6EU54tYD+4G39cUb28kxC6jMf/AbPfWGLQtgPTdh3xw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "node-addon-api": "^8.2.1",
+ "node-gyp-build": "^4.8.2"
+ },
+ "peerDependencies": {
+ "tree-sitter": "^0.25.0"
+ },
+ "peerDependenciesMeta": {
+ "tree-sitter": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tree-sitter-cli": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.25.10.tgz",
+ "integrity": "sha512-KoebQguKMCIghisEOdA372TIbrUl0kdnfZ9YQIBRAeOvNSKe85XbU4LuFW7hduRUwJj0rAG7pX5wo9sZhbBF1g==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "tree-sitter": "cli.js"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/tree-sitter-bashpp/package.json b/tree-sitter-bashpp/package.json
new file mode 100644
index 0000000..de2bcaa
--- /dev/null
+++ b/tree-sitter-bashpp/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "tree-sitter-bashpp",
+ "version": "0.1.0",
+ "description": "Bash++ grammar for Tree-sitter",
+ "license": "GPL-3.0-or-later",
+ "author": "Bash++ contributors",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/rail5/bashpp.git",
+ "directory": "tree-sitter-bashpp"
+ },
+ "keywords": [
+ "bashpp",
+ "incremental",
+ "parser",
+ "tree-sitter"
+ ],
+ "files": [
+ "grammar.js",
+ "tree-sitter.json",
+ "src/**",
+ "LICENSE",
+ "README.md",
+ "THIRD_PARTY_LICENSES/**"
+ ],
+ "scripts": {
+ "generate": "tree-sitter generate",
+ "lint": "eslint grammar.js",
+ "test": "tree-sitter test -r",
+ "test:repository": "bash test/parse-repository-files.sh"
+ },
+ "dependencies": {
+ "tree-sitter-bash": "0.25.1"
+ },
+ "devDependencies": {
+ "eslint": "9.39.1",
+ "eslint-config-treesitter": "1.0.2",
+ "tree-sitter-cli": "0.25.10"
+ }
+}
diff --git a/tree-sitter-bashpp/src/scanner.c b/tree-sitter-bashpp/src/scanner.c
new file mode 100644
index 0000000..ec2466d
--- /dev/null
+++ b/tree-sitter-bashpp/src/scanner.c
@@ -0,0 +1,1398 @@
+#include "tree_sitter/array.h"
+#include "tree_sitter/parser.h"
+
+#include
+#include
+#include
+#include
+
+enum TokenType {
+ HEREDOC_START,
+ SIMPLE_HEREDOC_BODY,
+ HEREDOC_BODY_BEGINNING,
+ HEREDOC_CONTENT,
+ HEREDOC_END,
+ FILE_DESCRIPTOR,
+ EMPTY_VALUE,
+ CONCAT,
+ VARIABLE_NAME,
+ TEST_OPERATOR,
+ REGEX,
+ REGEX_NO_SLASH,
+ REGEX_NO_SPACE,
+ EXPANSION_WORD,
+ EXTGLOB_PATTERN,
+ BARE_DOLLAR,
+ BRACE_START,
+ IMMEDIATE_DOUBLE_HASH,
+ EXTERNAL_EXPANSION_SYM_HASH,
+ EXTERNAL_EXPANSION_SYM_BANG,
+ EXTERNAL_EXPANSION_SYM_EQUAL,
+ CLOSING_BRACE,
+ CLOSING_BRACKET,
+ HEREDOC_ARROW,
+ HEREDOC_ARROW_DASH,
+ NEWLINE,
+ OPENING_PAREN,
+ ESAC,
+ ERROR_RECOVERY,
+ BASHPP_OBJECT_TYPE,
+ BASHPP_POINTER_TYPE,
+ BASHPP_ASSIGNMENT_REFERENCE,
+ BASHPP_ASSIGNMENT_OPERATOR,
+ BASHPP_REFERENCE_END,
+};
+
+typedef Array(char) String;
+
+typedef struct {
+ bool is_raw;
+ bool started;
+ bool allows_indent;
+ String delimiter;
+ String current_leading_word;
+} Heredoc;
+
+#define heredoc_new() \
+ { \
+ .is_raw = false, \
+ .started = false, \
+ .allows_indent = false, \
+ .delimiter = array_new(), \
+ .current_leading_word = array_new(), \
+ };
+
+typedef struct {
+ uint8_t last_glob_paren_depth;
+ bool ext_was_in_double_quote;
+ bool ext_saw_outside_quote;
+ Array(Heredoc) heredocs;
+} Scanner;
+
+static inline void advance(TSLexer *lexer) { lexer->advance(lexer, false); }
+
+static inline void skip(TSLexer *lexer) { lexer->advance(lexer, true); }
+
+static inline bool in_error_recovery(const bool *valid_symbols) { return valid_symbols[ERROR_RECOVERY]; }
+
+static bool is_bashpp_keyword(const char *identifier, size_t length) {
+ static const char *keywords[] = {
+ "class", "constructor", "delete", "destructor", "dynamic_cast",
+ "include", "include_once", "method", "new", "nullptr",
+ "private", "protected", "public", "super", "this",
+ "typeof", "virtual",
+ };
+
+ for (size_t index = 0; index < sizeof(keywords) / sizeof(keywords[0]); index++) {
+ if (strlen(keywords[index]) == length && strncmp(keywords[index], identifier, length) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static inline void reset_string(String *string) {
+ if (string->size > 0) {
+ memset(string->contents, 0, string->size);
+ array_clear(string);
+ }
+}
+
+static inline void reset_heredoc(Heredoc *heredoc) {
+ heredoc->is_raw = false;
+ heredoc->started = false;
+ heredoc->allows_indent = false;
+ reset_string(&heredoc->delimiter);
+}
+
+static inline void reset(Scanner *scanner) {
+ for (uint32_t i = 0; i < scanner->heredocs.size; i++) {
+ reset_heredoc(array_get(&scanner->heredocs, i));
+ }
+}
+
+static unsigned serialize(Scanner *scanner, char *buffer) {
+ uint32_t size = 0;
+
+ buffer[size++] = (char)scanner->last_glob_paren_depth;
+ buffer[size++] = (char)scanner->ext_was_in_double_quote;
+ buffer[size++] = (char)scanner->ext_saw_outside_quote;
+ buffer[size++] = (char)scanner->heredocs.size;
+
+ for (uint32_t i = 0; i < scanner->heredocs.size; i++) {
+ Heredoc *heredoc = array_get(&scanner->heredocs, i);
+ if (size + 3 + sizeof(uint32_t) + heredoc->delimiter.size >= TREE_SITTER_SERIALIZATION_BUFFER_SIZE) {
+ return 0;
+ }
+
+ buffer[size++] = (char)heredoc->is_raw;
+ buffer[size++] = (char)heredoc->started;
+ buffer[size++] = (char)heredoc->allows_indent;
+
+ memcpy(&buffer[size], &heredoc->delimiter.size, sizeof(uint32_t));
+ size += sizeof(uint32_t);
+ if (heredoc->delimiter.size > 0) {
+ memcpy(&buffer[size], heredoc->delimiter.contents, heredoc->delimiter.size);
+ size += heredoc->delimiter.size;
+ }
+ }
+ return size;
+}
+
+static void deserialize(Scanner *scanner, const char *buffer, unsigned length) {
+ if (length == 0) {
+ reset(scanner);
+ } else {
+ uint32_t size = 0;
+ scanner->last_glob_paren_depth = buffer[size++];
+ scanner->ext_was_in_double_quote = buffer[size++];
+ scanner->ext_saw_outside_quote = buffer[size++];
+ uint32_t heredoc_count = (unsigned char)buffer[size++];
+ for (uint32_t i = 0; i < heredoc_count; i++) {
+ Heredoc *heredoc = NULL;
+ if (i < scanner->heredocs.size) {
+ heredoc = array_get(&scanner->heredocs, i);
+ } else {
+ Heredoc new_heredoc = heredoc_new();
+ array_push(&scanner->heredocs, new_heredoc);
+ heredoc = array_back(&scanner->heredocs);
+ }
+
+ heredoc->is_raw = buffer[size++];
+ heredoc->started = buffer[size++];
+ heredoc->allows_indent = buffer[size++];
+
+ memcpy(&heredoc->delimiter.size, &buffer[size], sizeof(uint32_t));
+ size += sizeof(uint32_t);
+ array_reserve(&heredoc->delimiter, heredoc->delimiter.size);
+
+ if (heredoc->delimiter.size > 0) {
+ memcpy(heredoc->delimiter.contents, &buffer[size], heredoc->delimiter.size);
+ size += heredoc->delimiter.size;
+ }
+ }
+ assert(size == length);
+ }
+}
+
+/**
+ * Consume a "word" in POSIX parlance, and returns it unquoted.
+ *
+ * This is an approximate implementation that doesn't deal with any
+ * POSIX-mandated substitution, and assumes the default value for
+ * IFS.
+ */
+static bool advance_word(TSLexer *lexer, String *unquoted_word) {
+ bool empty = true;
+
+ int32_t quote = 0;
+ if (lexer->lookahead == '\'' || lexer->lookahead == '"') {
+ quote = lexer->lookahead;
+ advance(lexer);
+ }
+
+ while (lexer->lookahead &&
+ !(quote ? lexer->lookahead == quote || lexer->lookahead == '\r' || lexer->lookahead == '\n'
+ : iswspace(lexer->lookahead))) {
+ if (lexer->lookahead == '\\') {
+ advance(lexer);
+ if (!lexer->lookahead) {
+ return false;
+ }
+ }
+ empty = false;
+ array_push(unquoted_word, lexer->lookahead);
+ advance(lexer);
+ }
+ array_push(unquoted_word, '\0');
+
+ if (quote && lexer->lookahead == quote) {
+ advance(lexer);
+ }
+
+ return !empty;
+}
+
+static inline bool scan_bare_dollar(TSLexer *lexer) {
+ while (iswspace(lexer->lookahead) && lexer->lookahead != '\n' && !lexer->eof(lexer)) {
+ skip(lexer);
+ }
+
+ if (lexer->lookahead == '$') {
+ advance(lexer);
+ lexer->result_symbol = BARE_DOLLAR;
+ lexer->mark_end(lexer);
+ return iswspace(lexer->lookahead) || lexer->eof(lexer) || lexer->lookahead == '\"';
+ }
+
+ return false;
+}
+
+static bool scan_heredoc_start(Heredoc *heredoc, TSLexer *lexer) {
+ while (iswspace(lexer->lookahead)) {
+ skip(lexer);
+ }
+
+ lexer->result_symbol = HEREDOC_START;
+ heredoc->is_raw = lexer->lookahead == '\'' || lexer->lookahead == '"' || lexer->lookahead == '\\';
+
+ bool found_delimiter = advance_word(lexer, &heredoc->delimiter);
+ if (!found_delimiter) {
+ reset_string(&heredoc->delimiter);
+ return false;
+ }
+ return found_delimiter;
+}
+
+static bool scan_heredoc_end_identifier(Heredoc *heredoc, TSLexer *lexer) {
+ reset_string(&heredoc->current_leading_word);
+ // Scan the first 'n' characters on this line, to see if they match the
+ // heredoc delimiter
+ int32_t size = 0;
+ if (heredoc->delimiter.size > 0) {
+ while (lexer->lookahead != '\0' && lexer->lookahead != '\n' &&
+ (int32_t)*array_get(&heredoc->delimiter, size) == lexer->lookahead &&
+ heredoc->current_leading_word.size < heredoc->delimiter.size) {
+ array_push(&heredoc->current_leading_word, lexer->lookahead);
+ advance(lexer);
+ size++;
+ }
+ }
+ array_push(&heredoc->current_leading_word, '\0');
+ return heredoc->delimiter.size == 0
+ ? false
+ : strcmp(heredoc->current_leading_word.contents, heredoc->delimiter.contents) == 0;
+}
+
+static bool scan_heredoc_content(Scanner *scanner, TSLexer *lexer, enum TokenType middle_type,
+ enum TokenType end_type) {
+ bool did_advance = false;
+ Heredoc *heredoc = array_back(&scanner->heredocs);
+
+ for (;;) {
+ switch (lexer->lookahead) {
+ case '\0': {
+ if (lexer->eof(lexer) && did_advance) {
+ reset_heredoc(heredoc);
+ lexer->result_symbol = end_type;
+ return true;
+ }
+ return false;
+ }
+
+ case '\\': {
+ did_advance = true;
+ advance(lexer);
+ advance(lexer);
+ break;
+ }
+
+ case '$': {
+ if (heredoc->is_raw) {
+ did_advance = true;
+ advance(lexer);
+ break;
+ }
+ if (did_advance) {
+ lexer->mark_end(lexer);
+ lexer->result_symbol = middle_type;
+ heredoc->started = true;
+ advance(lexer);
+ if (iswalpha(lexer->lookahead) || lexer->lookahead == '{' || lexer->lookahead == '(') {
+ return true;
+ }
+ break;
+ }
+ if (middle_type == HEREDOC_BODY_BEGINNING && lexer->get_column(lexer) == 0) {
+ lexer->result_symbol = middle_type;
+ heredoc->started = true;
+ return true;
+ }
+ return false;
+ }
+
+ case '@': {
+ if (heredoc->is_raw) {
+ did_advance = true;
+ advance(lexer);
+ break;
+ }
+ if (did_advance) {
+ lexer->mark_end(lexer);
+ lexer->result_symbol = middle_type;
+ heredoc->started = true;
+ return true;
+ }
+ if (middle_type == HEREDOC_BODY_BEGINNING && lexer->get_column(lexer) == 0) {
+ lexer->result_symbol = middle_type;
+ heredoc->started = true;
+ return true;
+ }
+ return false;
+ }
+
+ case '\n': {
+ if (!did_advance) {
+ skip(lexer);
+ } else {
+ advance(lexer);
+ }
+ did_advance = true;
+ if (heredoc->allows_indent) {
+ while (iswspace(lexer->lookahead)) {
+ advance(lexer);
+ }
+ }
+ lexer->result_symbol = heredoc->started ? middle_type : end_type;
+ lexer->mark_end(lexer);
+ if (scan_heredoc_end_identifier(heredoc, lexer)) {
+ if (lexer->result_symbol == HEREDOC_END) {
+ array_pop(&scanner->heredocs);
+ }
+ return true;
+ }
+ break;
+ }
+
+ default: {
+ if (lexer->get_column(lexer) == 0) {
+ // an alternative is to check the starting column of the
+ // heredoc body and track that statefully
+ while (iswspace(lexer->lookahead)) {
+ if (did_advance) {
+ advance(lexer);
+ } else {
+ skip(lexer);
+ }
+ }
+ if (end_type != SIMPLE_HEREDOC_BODY) {
+ lexer->result_symbol = middle_type;
+ if (scan_heredoc_end_identifier(heredoc, lexer)) {
+ return true;
+ }
+ }
+ if (end_type == SIMPLE_HEREDOC_BODY) {
+ lexer->result_symbol = end_type;
+ lexer->mark_end(lexer);
+ if (scan_heredoc_end_identifier(heredoc, lexer)) {
+ return true;
+ }
+ }
+ }
+ did_advance = true;
+ advance(lexer);
+ break;
+ }
+ }
+ }
+}
+
+static bool scan(Scanner *scanner, TSLexer *lexer, const bool *valid_symbols) {
+ if (valid_symbols[NEWLINE] && lexer->lookahead == '\n') {
+ advance(lexer);
+ lexer->mark_end(lexer);
+ lexer->result_symbol = NEWLINE;
+ return true;
+ }
+
+ bool can_scan_type = valid_symbols[BASHPP_OBJECT_TYPE] || valid_symbols[BASHPP_POINTER_TYPE];
+ bool can_scan_assignment = valid_symbols[BASHPP_ASSIGNMENT_REFERENCE];
+ if (can_scan_type || can_scan_assignment) {
+ while (iswspace(lexer->lookahead) &&
+ (lexer->lookahead != '\n' || !valid_symbols[NEWLINE])) {
+ skip(lexer);
+ }
+
+ bool starts_with_dereference = lexer->lookahead == '*';
+ if (starts_with_dereference) {
+ if (!can_scan_assignment) {
+ return false;
+ }
+ advance(lexer);
+ }
+
+ if (lexer->lookahead != '@') {
+ goto bash_scanner;
+ }
+ advance(lexer);
+ if (!iswalpha(lexer->lookahead) && lexer->lookahead != '_') {
+ return false;
+ }
+
+ char identifier[32];
+ size_t identifier_length = 0;
+ while (iswalnum(lexer->lookahead) || lexer->lookahead == '_') {
+ if (identifier_length < sizeof(identifier)) {
+ identifier[identifier_length++] = (char)lexer->lookahead;
+ }
+ advance(lexer);
+ }
+
+ bool has_member = false;
+ while (lexer->lookahead == '.') {
+ has_member = true;
+ advance(lexer);
+ if (!iswalpha(lexer->lookahead) && lexer->lookahead != '_') {
+ return false;
+ }
+ while (iswalnum(lexer->lookahead) || lexer->lookahead == '_') {
+ advance(lexer);
+ }
+ }
+ lexer->mark_end(lexer);
+
+ bool has_index = lexer->lookahead == '[';
+ if (can_scan_assignment && has_index) {
+ uint32_t bracket_depth = 0;
+ do {
+ if (lexer->lookahead == '[') {
+ bracket_depth++;
+ } else if (lexer->lookahead == ']') {
+ bracket_depth--;
+ } else if (lexer->lookahead == '\\') {
+ advance(lexer);
+ }
+ advance(lexer);
+ } while (bracket_depth > 0 && !lexer->eof(lexer));
+ }
+
+ if (can_scan_assignment && lexer->lookahead == '=') {
+ lexer->result_symbol = BASHPP_ASSIGNMENT_REFERENCE;
+ return true;
+ }
+ if (can_scan_assignment && lexer->lookahead == '+') {
+ advance(lexer);
+ if (lexer->lookahead == '=') {
+ lexer->result_symbol = BASHPP_ASSIGNMENT_REFERENCE;
+ return true;
+ }
+ return false;
+ }
+ if (starts_with_dereference || has_member || has_index ||
+ is_bashpp_keyword(identifier, identifier_length)) {
+ return false;
+ }
+
+ if (lexer->lookahead == '*' && valid_symbols[BASHPP_POINTER_TYPE]) {
+ advance(lexer);
+ lexer->mark_end(lexer);
+ if (lexer->lookahead != ' ' && lexer->lookahead != '\t') {
+ return false;
+ }
+ while (lexer->lookahead == ' ' || lexer->lookahead == '\t') {
+ advance(lexer);
+ }
+ if (iswalpha(lexer->lookahead) || lexer->lookahead == '_') {
+ lexer->result_symbol = BASHPP_POINTER_TYPE;
+ return true;
+ }
+ return false;
+ }
+
+ if (!valid_symbols[BASHPP_OBJECT_TYPE] ||
+ (lexer->lookahead != ' ' && lexer->lookahead != '\t')) {
+ return false;
+ }
+ while (lexer->lookahead == ' ' || lexer->lookahead == '\t') {
+ advance(lexer);
+ }
+ if (iswalpha(lexer->lookahead) || lexer->lookahead == '_') {
+ lexer->result_symbol = BASHPP_OBJECT_TYPE;
+ return true;
+ }
+ return false;
+ }
+
+bash_scanner:
+ if (valid_symbols[BASHPP_ASSIGNMENT_OPERATOR]) {
+ if (lexer->lookahead == '=') {
+ advance(lexer);
+ lexer->mark_end(lexer);
+ lexer->result_symbol = BASHPP_ASSIGNMENT_OPERATOR;
+ return true;
+ }
+ if (lexer->lookahead == '+') {
+ advance(lexer);
+ if (lexer->lookahead == '=') {
+ advance(lexer);
+ lexer->mark_end(lexer);
+ lexer->result_symbol = BASHPP_ASSIGNMENT_OPERATOR;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ if (valid_symbols[BASHPP_REFERENCE_END] && lexer->lookahead != '.' && lexer->lookahead != '[') {
+ lexer->result_symbol = BASHPP_REFERENCE_END;
+ return true;
+ }
+
+ if (valid_symbols[CONCAT] && !in_error_recovery(valid_symbols)) {
+ if (!(lexer->lookahead == 0 || iswspace(lexer->lookahead) || lexer->lookahead == '>' ||
+ lexer->lookahead == '<' || lexer->lookahead == ')' || lexer->lookahead == '(' ||
+ lexer->lookahead == ';' || lexer->lookahead == '&' || lexer->lookahead == '|' ||
+ (lexer->lookahead == '}' && valid_symbols[CLOSING_BRACE]) ||
+ (lexer->lookahead == ']' && valid_symbols[CLOSING_BRACKET]))) {
+ lexer->result_symbol = CONCAT;
+ // So for a`b`, we want to return a concat. We check if the
+ // 2nd backtick has whitespace after it, and if it does we
+ // return concat.
+ if (lexer->lookahead == '`') {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ while (lexer->lookahead != '`' && !lexer->eof(lexer)) {
+ advance(lexer);
+ }
+ if (lexer->eof(lexer)) {
+ return false;
+ }
+ if (lexer->lookahead == '`') {
+ advance(lexer);
+ }
+ return iswspace(lexer->lookahead) || lexer->eof(lexer);
+ }
+ // strings w/ expansions that contains escaped quotes or
+ // backslashes need this to return a concat
+ if (lexer->lookahead == '\\') {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ if (lexer->lookahead == '"' || lexer->lookahead == '\'' || lexer->lookahead == '\\') {
+ return true;
+ }
+ if (lexer->eof(lexer)) {
+ return false;
+ }
+ } else {
+ return true;
+ }
+ }
+ if (iswspace(lexer->lookahead) && valid_symbols[CLOSING_BRACE] && !valid_symbols[EXPANSION_WORD]) {
+ lexer->result_symbol = CONCAT;
+ return true;
+ }
+ }
+
+ if (valid_symbols[IMMEDIATE_DOUBLE_HASH] && !in_error_recovery(valid_symbols)) {
+ // advance two # and ensure not } after
+ if (lexer->lookahead == '#') {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ if (lexer->lookahead == '#') {
+ advance(lexer);
+ if (lexer->lookahead != '}') {
+ lexer->result_symbol = IMMEDIATE_DOUBLE_HASH;
+ lexer->mark_end(lexer);
+ return true;
+ }
+ }
+ }
+ }
+
+ if (valid_symbols[EXTERNAL_EXPANSION_SYM_HASH] && !in_error_recovery(valid_symbols)) {
+ if (lexer->lookahead == '#' || lexer->lookahead == '=' || lexer->lookahead == '!') {
+ lexer->result_symbol = lexer->lookahead == '#' ? EXTERNAL_EXPANSION_SYM_HASH
+ : lexer->lookahead == '!' ? EXTERNAL_EXPANSION_SYM_BANG
+ : EXTERNAL_EXPANSION_SYM_EQUAL;
+ advance(lexer);
+ lexer->mark_end(lexer);
+ while (lexer->lookahead == '#' || lexer->lookahead == '=' || lexer->lookahead == '!') {
+ advance(lexer);
+ }
+ while (iswspace(lexer->lookahead)) {
+ skip(lexer);
+ }
+ if (lexer->lookahead == '}') {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ if (valid_symbols[EMPTY_VALUE]) {
+ if (iswspace(lexer->lookahead) || lexer->eof(lexer) || lexer->lookahead == ';' || lexer->lookahead == '&') {
+ lexer->result_symbol = EMPTY_VALUE;
+ return true;
+ }
+ }
+
+ if ((valid_symbols[HEREDOC_BODY_BEGINNING] || valid_symbols[SIMPLE_HEREDOC_BODY]) && scanner->heredocs.size > 0 &&
+ !array_back(&scanner->heredocs)->started && !in_error_recovery(valid_symbols)) {
+ return scan_heredoc_content(scanner, lexer, HEREDOC_BODY_BEGINNING, SIMPLE_HEREDOC_BODY);
+ }
+
+ if (valid_symbols[HEREDOC_END] && scanner->heredocs.size > 0) {
+ Heredoc *heredoc = array_back(&scanner->heredocs);
+ if (scan_heredoc_end_identifier(heredoc, lexer)) {
+ array_delete(&heredoc->current_leading_word);
+ array_delete(&heredoc->delimiter);
+ array_pop(&scanner->heredocs);
+ lexer->result_symbol = HEREDOC_END;
+ return true;
+ }
+ }
+
+ if (valid_symbols[HEREDOC_CONTENT] && scanner->heredocs.size > 0 && array_back(&scanner->heredocs)->started &&
+ !in_error_recovery(valid_symbols)) {
+ return scan_heredoc_content(scanner, lexer, HEREDOC_CONTENT, HEREDOC_END);
+ }
+
+ if (valid_symbols[HEREDOC_START] && !in_error_recovery(valid_symbols) && scanner->heredocs.size > 0) {
+ return scan_heredoc_start(array_back(&scanner->heredocs), lexer);
+ }
+
+ if (valid_symbols[TEST_OPERATOR] && !valid_symbols[EXPANSION_WORD]) {
+ while (iswspace(lexer->lookahead) && lexer->lookahead != '\n') {
+ skip(lexer);
+ }
+
+ if (lexer->lookahead == '\\') {
+ if (valid_symbols[EXTGLOB_PATTERN]) {
+ goto extglob_pattern;
+ }
+ if (valid_symbols[REGEX_NO_SPACE]) {
+ goto regex;
+ }
+ skip(lexer);
+
+ if (lexer->eof(lexer)) {
+ return false;
+ }
+
+ if (lexer->lookahead == '\r') {
+ skip(lexer);
+ if (lexer->lookahead == '\n') {
+ skip(lexer);
+ }
+ } else if (lexer->lookahead == '\n') {
+ skip(lexer);
+ } else {
+ return false;
+ }
+
+ while (iswspace(lexer->lookahead)) {
+ skip(lexer);
+ }
+ }
+
+ if (lexer->lookahead == '\n' && !valid_symbols[NEWLINE]) {
+ skip(lexer);
+
+ while (iswspace(lexer->lookahead)) {
+ skip(lexer);
+ }
+ }
+
+ if (lexer->lookahead == '-') {
+ advance(lexer);
+
+ bool advanced_once = false;
+ while (iswalpha(lexer->lookahead)) {
+ advanced_once = true;
+ advance(lexer);
+ }
+
+ if (iswspace(lexer->lookahead) && advanced_once) {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ if (lexer->lookahead == '}' && valid_symbols[CLOSING_BRACE]) {
+ if (valid_symbols[EXPANSION_WORD]) {
+ lexer->mark_end(lexer);
+ lexer->result_symbol = EXPANSION_WORD;
+ return true;
+ }
+ return false;
+ }
+ lexer->result_symbol = TEST_OPERATOR;
+ return true;
+ }
+ if (iswspace(lexer->lookahead) && valid_symbols[EXTGLOB_PATTERN]) {
+ lexer->result_symbol = EXTGLOB_PATTERN;
+ return true;
+ }
+ }
+
+ if (valid_symbols[BARE_DOLLAR] && !in_error_recovery(valid_symbols) && scan_bare_dollar(lexer)) {
+ return true;
+ }
+ }
+
+ if ((valid_symbols[VARIABLE_NAME] || valid_symbols[FILE_DESCRIPTOR] || valid_symbols[HEREDOC_ARROW]) &&
+ !valid_symbols[REGEX_NO_SLASH] && !in_error_recovery(valid_symbols)) {
+ for (;;) {
+ if ((lexer->lookahead == ' ' || lexer->lookahead == '\t' || lexer->lookahead == '\r' ||
+ (lexer->lookahead == '\n' && !valid_symbols[NEWLINE])) &&
+ !valid_symbols[EXPANSION_WORD]) {
+ skip(lexer);
+ } else if (lexer->lookahead == '\\') {
+ skip(lexer);
+
+ if (lexer->eof(lexer)) {
+ lexer->mark_end(lexer);
+ lexer->result_symbol = VARIABLE_NAME;
+ return true;
+ }
+
+ if (lexer->lookahead == '\r') {
+ skip(lexer);
+ }
+ if (lexer->lookahead == '\n') {
+ skip(lexer);
+ } else {
+ if (lexer->lookahead == '\\' && valid_symbols[EXPANSION_WORD]) {
+ goto expansion_word;
+ }
+ return false;
+ }
+ } else {
+ break;
+ }
+ }
+
+ // no '*', '@', '?', '-', '$', '0', '_'
+ if (!valid_symbols[EXPANSION_WORD] &&
+ (lexer->lookahead == '*' || lexer->lookahead == '@' || lexer->lookahead == '?' || lexer->lookahead == '-' ||
+ lexer->lookahead == '0' || lexer->lookahead == '_')) {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ if (lexer->lookahead == '=' || lexer->lookahead == '[' || lexer->lookahead == ':' ||
+ lexer->lookahead == '-' || lexer->lookahead == '%' || lexer->lookahead == '#' ||
+ lexer->lookahead == '/') {
+ return false;
+ }
+ if (valid_symbols[EXTGLOB_PATTERN] && iswspace(lexer->lookahead)) {
+ lexer->mark_end(lexer);
+ lexer->result_symbol = EXTGLOB_PATTERN;
+ return true;
+ }
+ }
+
+ if (valid_symbols[HEREDOC_ARROW] && lexer->lookahead == '<') {
+ advance(lexer);
+ if (lexer->lookahead == '<') {
+ advance(lexer);
+ if (lexer->lookahead == '-') {
+ advance(lexer);
+ Heredoc heredoc = heredoc_new();
+ heredoc.allows_indent = true;
+ array_push(&scanner->heredocs, heredoc);
+ lexer->result_symbol = HEREDOC_ARROW_DASH;
+ } else if (lexer->lookahead == '<' || lexer->lookahead == '=') {
+ return false;
+ } else {
+ Heredoc heredoc = heredoc_new();
+ array_push(&scanner->heredocs, heredoc);
+ lexer->result_symbol = HEREDOC_ARROW;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ bool is_number = true;
+ if (iswdigit(lexer->lookahead)) {
+ advance(lexer);
+ } else if (iswalpha(lexer->lookahead) || lexer->lookahead == '_') {
+ is_number = false;
+ advance(lexer);
+ } else {
+ if (lexer->lookahead == '{') {
+ goto brace_start;
+ }
+ if (valid_symbols[EXPANSION_WORD]) {
+ goto expansion_word;
+ }
+ if (valid_symbols[EXTGLOB_PATTERN]) {
+ goto extglob_pattern;
+ }
+ return false;
+ }
+
+ for (;;) {
+ if (iswdigit(lexer->lookahead)) {
+ advance(lexer);
+ } else if (iswalpha(lexer->lookahead) || lexer->lookahead == '_') {
+ is_number = false;
+ advance(lexer);
+ } else {
+ break;
+ }
+ }
+
+ if (is_number && valid_symbols[FILE_DESCRIPTOR] && (lexer->lookahead == '>' || lexer->lookahead == '<')) {
+ lexer->result_symbol = FILE_DESCRIPTOR;
+ return true;
+ }
+
+ if (valid_symbols[VARIABLE_NAME]) {
+ if (lexer->lookahead == '+') {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ if (lexer->lookahead == '=' || lexer->lookahead == ':' || valid_symbols[CLOSING_BRACE]) {
+ lexer->result_symbol = VARIABLE_NAME;
+ return true;
+ }
+ return false;
+ }
+ if (lexer->lookahead == '/') {
+ return false;
+ }
+ if (lexer->lookahead == '=' || lexer->lookahead == '[' ||
+ (lexer->lookahead == ':' && !valid_symbols[CLOSING_BRACE] &&
+ !valid_symbols[OPENING_PAREN]) || // TODO(amaanq): more cases for regular word chars but not variable
+ // names for function words, only handling : for now? #235
+ lexer->lookahead == '%' ||
+ (lexer->lookahead == '#' && !is_number) || lexer->lookahead == '@' ||
+ (lexer->lookahead == '-' && valid_symbols[CLOSING_BRACE])) {
+ lexer->mark_end(lexer);
+ lexer->result_symbol = VARIABLE_NAME;
+ return true;
+ }
+
+ if (lexer->lookahead == '?') {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ lexer->result_symbol = VARIABLE_NAME;
+ return iswalpha(lexer->lookahead);
+ }
+ }
+
+ return false;
+ }
+
+ if (valid_symbols[BARE_DOLLAR] && !in_error_recovery(valid_symbols) && scan_bare_dollar(lexer)) {
+ return true;
+ }
+
+regex:
+ if ((valid_symbols[REGEX] || valid_symbols[REGEX_NO_SLASH] || valid_symbols[REGEX_NO_SPACE]) &&
+ !in_error_recovery(valid_symbols)) {
+ if (valid_symbols[REGEX] || valid_symbols[REGEX_NO_SPACE]) {
+ while (iswspace(lexer->lookahead)) {
+ skip(lexer);
+ }
+ }
+
+ if ((lexer->lookahead != '"' && lexer->lookahead != '\'') ||
+ ((lexer->lookahead == '$' || lexer->lookahead == '\'') && valid_symbols[REGEX_NO_SLASH]) ||
+ (lexer->lookahead == '\'' && valid_symbols[REGEX_NO_SPACE])) {
+ typedef struct {
+ bool done;
+ bool advanced_once;
+ bool found_non_alnumdollarunderdash;
+ bool last_was_escape;
+ bool in_single_quote;
+ uint32_t paren_depth;
+ uint32_t bracket_depth;
+ uint32_t brace_depth;
+ } State;
+
+ if (lexer->lookahead == '$' && valid_symbols[REGEX_NO_SLASH]) {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ if (lexer->lookahead == '(') {
+ return false;
+ }
+ }
+
+ lexer->mark_end(lexer);
+
+ State state = {false, false, false, false, false, 0, 0, 0};
+ while (!state.done) {
+ if (state.in_single_quote) {
+ if (lexer->lookahead == '\'') {
+ state.in_single_quote = false;
+ advance(lexer);
+ lexer->mark_end(lexer);
+ }
+ }
+ switch (lexer->lookahead) {
+ case '\\':
+ state.last_was_escape = true;
+ break;
+ case '\0':
+ return false;
+ case '(':
+ state.paren_depth++;
+ state.last_was_escape = false;
+ break;
+ case '[':
+ state.bracket_depth++;
+ state.last_was_escape = false;
+ break;
+ case '{':
+ if (!state.last_was_escape) {
+ state.brace_depth++;
+ }
+ state.last_was_escape = false;
+ break;
+ case ')':
+ if (state.paren_depth == 0) {
+ state.done = true;
+ }
+ state.paren_depth--;
+ state.last_was_escape = false;
+ break;
+ case ']':
+ if (state.bracket_depth == 0) {
+ state.done = true;
+ }
+ state.bracket_depth--;
+ state.last_was_escape = false;
+ break;
+ case '}':
+ if (state.brace_depth == 0) {
+ state.done = true;
+ }
+ state.brace_depth--;
+ state.last_was_escape = false;
+ break;
+ case '\'':
+ // Enter or exit a single-quoted string.
+ state.in_single_quote = !state.in_single_quote;
+ advance(lexer);
+ state.advanced_once = true;
+ state.last_was_escape = false;
+ continue;
+ default:
+ state.last_was_escape = false;
+ break;
+ }
+
+ if (!state.done) {
+ if (valid_symbols[REGEX]) {
+ bool was_space = !state.in_single_quote && iswspace(lexer->lookahead);
+ advance(lexer);
+ state.advanced_once = true;
+ if (!was_space || state.paren_depth > 0) {
+ lexer->mark_end(lexer);
+ }
+ } else if (valid_symbols[REGEX_NO_SLASH]) {
+ if (lexer->lookahead == '/') {
+ lexer->mark_end(lexer);
+ lexer->result_symbol = REGEX_NO_SLASH;
+ return state.advanced_once;
+ }
+ if (lexer->lookahead == '\\') {
+ advance(lexer);
+ state.advanced_once = true;
+ if (!lexer->eof(lexer) && lexer->lookahead != '[' && lexer->lookahead != '/') {
+ advance(lexer);
+ lexer->mark_end(lexer);
+ }
+ } else {
+ bool was_space = !state.in_single_quote && iswspace(lexer->lookahead);
+ advance(lexer);
+ state.advanced_once = true;
+ if (!was_space) {
+ lexer->mark_end(lexer);
+ }
+ }
+ } else if (valid_symbols[REGEX_NO_SPACE]) {
+ if (lexer->lookahead == '\\') {
+ state.found_non_alnumdollarunderdash = true;
+ advance(lexer);
+ if (!lexer->eof(lexer)) {
+ advance(lexer);
+ }
+ } else if (lexer->lookahead == '$') {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ // do not parse a command
+ // substitution
+ if (lexer->lookahead == '(') {
+ return false;
+ }
+ // end $ always means regex, e.g.
+ // 99999999$
+ if (iswspace(lexer->lookahead)) {
+ lexer->result_symbol = REGEX_NO_SPACE;
+ lexer->mark_end(lexer);
+ return true;
+ }
+ } else {
+ bool was_space = !state.in_single_quote && iswspace(lexer->lookahead);
+ if (was_space && state.paren_depth == 0) {
+ lexer->mark_end(lexer);
+ lexer->result_symbol = REGEX_NO_SPACE;
+ return state.found_non_alnumdollarunderdash;
+ }
+ if (!iswalnum(lexer->lookahead) && lexer->lookahead != '$' && lexer->lookahead != '-' &&
+ lexer->lookahead != '_') {
+ state.found_non_alnumdollarunderdash = true;
+ }
+ advance(lexer);
+ }
+ }
+ }
+ }
+
+ lexer->result_symbol = valid_symbols[REGEX_NO_SLASH] ? REGEX_NO_SLASH
+ : valid_symbols[REGEX_NO_SPACE] ? REGEX_NO_SPACE
+ : REGEX;
+ if (valid_symbols[REGEX] && !state.advanced_once) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+extglob_pattern:
+ if (valid_symbols[EXTGLOB_PATTERN] && !in_error_recovery(valid_symbols)) {
+ // first skip ws, then check for ? * + @ !
+ while (iswspace(lexer->lookahead)) {
+ skip(lexer);
+ }
+
+ if (lexer->lookahead == '?' || lexer->lookahead == '*' || lexer->lookahead == '+' || lexer->lookahead == '@' ||
+ lexer->lookahead == '!' || lexer->lookahead == '-' || lexer->lookahead == ')' || lexer->lookahead == '\\' ||
+ lexer->lookahead == '.' || lexer->lookahead == '[' || (iswalpha(lexer->lookahead))) {
+ if (lexer->lookahead == '\\') {
+ advance(lexer);
+ if ((iswspace(lexer->lookahead) || lexer->lookahead == '"') && lexer->lookahead != '\r' &&
+ lexer->lookahead != '\n') {
+ advance(lexer);
+ } else {
+ return false;
+ }
+ }
+
+ if (lexer->lookahead == ')' && scanner->last_glob_paren_depth == 0) {
+ lexer->mark_end(lexer);
+ advance(lexer);
+
+ if (iswspace(lexer->lookahead)) {
+ return false;
+ }
+ }
+
+ lexer->mark_end(lexer);
+ bool was_non_alpha = !iswalpha(lexer->lookahead);
+ if (lexer->lookahead != '[') {
+ // no esac
+ if (lexer->lookahead == 'e') {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ if (lexer->lookahead == 's') {
+ advance(lexer);
+ if (lexer->lookahead == 'a') {
+ advance(lexer);
+ if (lexer->lookahead == 'c') {
+ advance(lexer);
+ if (iswspace(lexer->lookahead)) {
+ return false;
+ }
+ }
+ }
+ }
+ } else {
+ advance(lexer);
+ }
+ }
+
+ // -\w is just a word, find something else special
+ if (lexer->lookahead == '-') {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ while (iswalnum(lexer->lookahead)) {
+ advance(lexer);
+ }
+
+ if (lexer->lookahead == ')' || lexer->lookahead == '\\' || lexer->lookahead == '.') {
+ return false;
+ }
+ lexer->mark_end(lexer);
+ }
+
+ // case item -) or *)
+ if (lexer->lookahead == ')' && scanner->last_glob_paren_depth == 0) {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ if (iswspace(lexer->lookahead)) {
+ lexer->result_symbol = EXTGLOB_PATTERN;
+ return was_non_alpha;
+ }
+ }
+
+ if (iswspace(lexer->lookahead)) {
+ lexer->mark_end(lexer);
+ lexer->result_symbol = EXTGLOB_PATTERN;
+ scanner->last_glob_paren_depth = 0;
+ return true;
+ }
+
+ if (lexer->lookahead == '$') {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ if (lexer->lookahead == '{' || lexer->lookahead == '(') {
+ lexer->result_symbol = EXTGLOB_PATTERN;
+ return true;
+ }
+ }
+
+ if (lexer->lookahead == '|') {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ lexer->result_symbol = EXTGLOB_PATTERN;
+ return true;
+ }
+
+ if (!iswalnum(lexer->lookahead) && lexer->lookahead != '(' && lexer->lookahead != '"' &&
+ lexer->lookahead != '[' && lexer->lookahead != '?' && lexer->lookahead != '/' &&
+ lexer->lookahead != '\\' && lexer->lookahead != '_' && lexer->lookahead != '*') {
+ return false;
+ }
+
+ typedef struct {
+ bool done;
+ bool saw_non_alphadot;
+ uint32_t paren_depth;
+ uint32_t bracket_depth;
+ uint32_t brace_depth;
+ } State;
+
+ State state = {false, was_non_alpha, scanner->last_glob_paren_depth, 0, 0};
+ while (!state.done) {
+ switch (lexer->lookahead) {
+ case '\0':
+ return false;
+ case '(':
+ state.paren_depth++;
+ break;
+ case '[':
+ state.bracket_depth++;
+ break;
+ case '{':
+ state.brace_depth++;
+ break;
+ case ')':
+ if (state.paren_depth == 0) {
+ state.done = true;
+ }
+ state.paren_depth--;
+ break;
+ case ']':
+ if (state.bracket_depth == 0) {
+ state.done = true;
+ }
+ state.bracket_depth--;
+ break;
+ case '}':
+ if (state.brace_depth == 0) {
+ state.done = true;
+ }
+ state.brace_depth--;
+ break;
+ }
+
+ if (lexer->lookahead == '|') {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ if (state.paren_depth == 0 && state.bracket_depth == 0 && state.brace_depth == 0) {
+ lexer->result_symbol = EXTGLOB_PATTERN;
+ return true;
+ }
+ }
+
+ if (!state.done) {
+ bool was_space = iswspace(lexer->lookahead);
+ if (lexer->lookahead == '$') {
+ lexer->mark_end(lexer);
+ if (!iswalpha(lexer->lookahead) && lexer->lookahead != '.' && lexer->lookahead != '\\') {
+ state.saw_non_alphadot = true;
+ }
+ advance(lexer);
+ if (lexer->lookahead == '(' || lexer->lookahead == '{') {
+ lexer->result_symbol = EXTGLOB_PATTERN;
+ scanner->last_glob_paren_depth = state.paren_depth;
+ return state.saw_non_alphadot;
+ }
+ }
+ if (was_space) {
+ lexer->mark_end(lexer);
+ lexer->result_symbol = EXTGLOB_PATTERN;
+ scanner->last_glob_paren_depth = 0;
+ return state.saw_non_alphadot;
+ }
+ if (lexer->lookahead == '"') {
+ lexer->mark_end(lexer);
+ lexer->result_symbol = EXTGLOB_PATTERN;
+ scanner->last_glob_paren_depth = 0;
+ return state.saw_non_alphadot;
+ }
+ if (lexer->lookahead == '\\') {
+ if (!iswalpha(lexer->lookahead) && lexer->lookahead != '.' && lexer->lookahead != '\\') {
+ state.saw_non_alphadot = true;
+ }
+ advance(lexer);
+ if (iswspace(lexer->lookahead) || lexer->lookahead == '"') {
+ advance(lexer);
+ }
+ } else {
+ if (!iswalpha(lexer->lookahead) && lexer->lookahead != '.' && lexer->lookahead != '\\') {
+ state.saw_non_alphadot = true;
+ }
+ advance(lexer);
+ }
+ if (!was_space) {
+ lexer->mark_end(lexer);
+ }
+ }
+ }
+
+ lexer->result_symbol = EXTGLOB_PATTERN;
+ scanner->last_glob_paren_depth = 0;
+ return state.saw_non_alphadot;
+ }
+ scanner->last_glob_paren_depth = 0;
+
+ return false;
+ }
+
+expansion_word:
+ if (valid_symbols[EXPANSION_WORD]) {
+ bool advanced_once = false;
+ bool advance_once_space = false;
+ for (;;) {
+ if (lexer->lookahead == '\"') {
+ return false;
+ }
+ if (lexer->lookahead == '$') {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ if (lexer->lookahead == '{' || lexer->lookahead == '(' || lexer->lookahead == '\'' ||
+ iswalnum(lexer->lookahead)) {
+ lexer->result_symbol = EXPANSION_WORD;
+ return advanced_once;
+ }
+ advanced_once = true;
+ }
+
+ if (lexer->lookahead == '}') {
+ lexer->mark_end(lexer);
+ lexer->result_symbol = EXPANSION_WORD;
+ return advanced_once || advance_once_space;
+ }
+
+ if (lexer->lookahead == '(' && !(advanced_once || advance_once_space)) {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ while (lexer->lookahead != ')' && !lexer->eof(lexer)) {
+ // if we find a $( or ${ assume this is valid and is
+ // a garbage concatenation of some weird word + an
+ // expansion
+ // I wonder where this can fail
+ if (lexer->lookahead == '$') {
+ lexer->mark_end(lexer);
+ advance(lexer);
+ if (lexer->lookahead == '{' || lexer->lookahead == '(' || lexer->lookahead == '\'' ||
+ iswalnum(lexer->lookahead)) {
+ lexer->result_symbol = EXPANSION_WORD;
+ return advanced_once;
+ }
+ advanced_once = true;
+ } else {
+ advanced_once = advanced_once || !iswspace(lexer->lookahead);
+ advance_once_space = advance_once_space || iswspace(lexer->lookahead);
+ advance(lexer);
+ }
+ }
+ lexer->mark_end(lexer);
+ if (lexer->lookahead == ')') {
+ advanced_once = true;
+ advance(lexer);
+ lexer->mark_end(lexer);
+ if (lexer->lookahead == '}') {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ if (lexer->lookahead == '\'') {
+ return false;
+ }
+
+ if (lexer->eof(lexer)) {
+ return false;
+ }
+ advanced_once = advanced_once || !iswspace(lexer->lookahead);
+ advance_once_space = advance_once_space || iswspace(lexer->lookahead);
+ advance(lexer);
+ }
+ }
+
+brace_start:
+ if (valid_symbols[BRACE_START] && !in_error_recovery(valid_symbols)) {
+ while (iswspace(lexer->lookahead)) {
+ skip(lexer);
+ }
+
+ if (lexer->lookahead != '{') {
+ return false;
+ }
+
+ advance(lexer);
+ lexer->mark_end(lexer);
+
+ while (isdigit(lexer->lookahead)) {
+ advance(lexer);
+ }
+
+ if (lexer->lookahead != '.') {
+ return false;
+ }
+ advance(lexer);
+
+ if (lexer->lookahead != '.') {
+ return false;
+ }
+ advance(lexer);
+
+ while (isdigit(lexer->lookahead)) {
+ advance(lexer);
+ }
+
+ if (lexer->lookahead != '}') {
+ return false;
+ }
+
+ lexer->result_symbol = BRACE_START;
+ return true;
+ }
+
+ return false;
+}
+
+void *tree_sitter_bashpp_external_scanner_create() {
+ Scanner *scanner = calloc(1, sizeof(Scanner));
+ array_init(&scanner->heredocs);
+ return scanner;
+}
+
+bool tree_sitter_bashpp_external_scanner_scan(void *payload, TSLexer *lexer, const bool *valid_symbols) {
+ Scanner *scanner = (Scanner *)payload;
+ return scan(scanner, lexer, valid_symbols);
+}
+
+unsigned tree_sitter_bashpp_external_scanner_serialize(void *payload, char *state) {
+ Scanner *scanner = (Scanner *)payload;
+ return serialize(scanner, state);
+}
+
+void tree_sitter_bashpp_external_scanner_deserialize(void *payload, const char *state, unsigned length) {
+ Scanner *scanner = (Scanner *)payload;
+ deserialize(scanner, state, length);
+}
+
+void tree_sitter_bashpp_external_scanner_destroy(void *payload) {
+ Scanner *scanner = (Scanner *)payload;
+ for (size_t i = 0; i < scanner->heredocs.size; i++) {
+ Heredoc *heredoc = array_get(&scanner->heredocs, i);
+ array_delete(&heredoc->current_leading_word);
+ array_delete(&heredoc->delimiter);
+ }
+ array_delete(&scanner->heredocs);
+ free(scanner);
+}
diff --git a/tree-sitter-bashpp/test/corpus/bash-interaction.txt b/tree-sitter-bashpp/test/corpus/bash-interaction.txt
new file mode 100644
index 0000000..519350f
--- /dev/null
+++ b/tree-sitter-bashpp/test/corpus/bash-interaction.txt
@@ -0,0 +1,71 @@
+==================
+Bash arrays and arithmetic loops
+==================
+
+items=(one two)
+echo "${items[@]}"
+for ((i=@object.lower;i<@object.upper;i++)); do
+ echo "$i"
+done
+
+---
+
+(program
+ (variable_assignment
+ (variable_name)
+ (array
+ (word)
+ (word)))
+ (command
+ (command_name
+ (word))
+ (string
+ (expansion
+ (subscript
+ (variable_name)
+ (word)))))
+ (c_style_for_statement
+ (variable_assignment
+ (variable_name)
+ (object_reference
+ (reference_name)))
+ (binary_expression
+ (word)
+ (object_reference
+ (reference_name)))
+ (postfix_expression
+ (word))
+ (do_group
+ (command
+ (command_name
+ (word))
+ (string
+ (simple_expansion
+ (variable_name)))))))
+
+==================
+Command-like braced assignment and pointer cast
+==================
+
+@{object.values[0]}="command"
+shell_pointer=@dynamic_cast