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 &@object + +--- + +(program + (command + (command_name + (braced_reference + (reference_name) + (reference_index + (number))) + (assignment_operator) + (string + (string_content)))) + (variable_assignment + (variable_name) + (dynamic_cast_expression + (dynamic_cast_target + (type_identifier)) + (address_expression + (reference_name))))) diff --git a/tree-sitter-bashpp/test/corpus/bash.txt b/tree-sitter-bashpp/test/corpus/bash.txt new file mode 100644 index 0000000..3e0dce5 --- /dev/null +++ b/tree-sitter-bashpp/test/corpus/bash.txt @@ -0,0 +1,33 @@ +================== +Shell script +================== + +#!/usr/bin/env bpp + +name="Bash++" +if [[ -n "$name" ]]; then + printf '%s\n' "$name" +fi + +--- + +(program + (comment) + (variable_assignment + name: (variable_name) + value: (string + (string_content))) + (if_statement + condition: (test_command + (unary_expression + operator: (test_operator) + (string + (simple_expansion + (variable_name))))) + (command + name: (command_name + (word)) + argument: (raw_string) + argument: (string + (simple_expansion + (variable_name)))))) diff --git a/tree-sitter-bashpp/test/corpus/declarations.txt b/tree-sitter-bashpp/test/corpus/declarations.txt new file mode 100644 index 0000000..e44273c --- /dev/null +++ b/tree-sitter-bashpp/test/corpus/declarations.txt @@ -0,0 +1,142 @@ +================== +Class with inheritance and members +================== + +@class Child : Parent { + @public name="value" + @private @Object object + @protected @Object* pointer +} + +--- + +(program + (class_definition + (type_identifier) + (type_identifier) + (compound_statement + (data_member_declaration + (access_modifier) + (member_name) + (default_value + (assignment_operator) + (string + (string_content)))) + (data_member_declaration + (access_modifier) + (object_declaration + (type_identifier) + (object_name))) + (data_member_declaration + (access_modifier) + (pointer_declaration + (type_identifier) + (object_name)))))) + +================== +Methods and lifecycle definitions +================== + +@class Example { + @virtual @public @method update value @Object* object { + printf '%s\n' "$value" + } + + @constructor { + echo constructed + } + + @destructor { + echo destroyed + } +} + +--- + +(program + (class_definition + (type_identifier) + (compound_statement + (method_definition + (virtual_modifier) + (access_modifier) + (method_name) + (method_parameter + (parameter_name)) + (method_parameter + (type_identifier) + (parameter_name)) + (compound_statement + (command + (command_name + (word)) + (raw_string) + (string + (simple_expansion + (variable_name)))))) + (constructor_definition + (compound_statement + (command + (command_name + (word)) + (word)))) + (destructor_definition + (compound_statement + (command + (command_name + (word)) + (word))))))) + +================== +Object and pointer declarations +================== + +@Object object +@Object second="value" +@Object* pointer +@Object* initialized="address" + +--- + +(program + (object_declaration + (type_identifier) + (object_name)) + (object_declaration + (type_identifier) + (object_name) + (default_value + (assignment_operator) + (string + (string_content)))) + (pointer_declaration + (type_identifier) + (object_name)) + (pointer_declaration + (type_identifier) + (object_name) + (default_value + (assignment_operator) + (string + (string_content))))) + +================== +Include statements +================== + +@include "local.bpp" +@include_once static +@include dynamic as "/runtime/Queue.sh" + +--- + +(program + (include_statement + (include_path)) + (include_statement + (include_type) + (include_path)) + (include_statement + (include_type) + (include_path) + (include_path))) diff --git a/tree-sitter-bashpp/test/corpus/expressions.txt b/tree-sitter-bashpp/test/corpus/expressions.txt new file mode 100644 index 0000000..704cb53 --- /dev/null +++ b/tree-sitter-bashpp/test/corpus/expressions.txt @@ -0,0 +1,204 @@ +================== +References and assignments +================== + +@object.member +@this.member="value" +@super.method +@{object.array[$index]} +@{#this.array[@]} +*@pointer.member=@object.member +echo &@object.method + +--- + +(program + (command + (command_name + (object_reference + (reference_name)))) + (object_assignment + (assignment_reference + (reference_name)) + (assignment_operator) + (string + (string_content))) + (command + (command_name + (self_reference + (super)))) + (command + (command_name + (braced_reference + (reference_name) + (reference_index + (simple_expansion + (variable_name)))))) + (command + (command_name + (braced_reference + (reference_name) + (reference_index)))) + (object_assignment + (assignment_reference + (reference_name)) + (assignment_operator) + (object_reference + (reference_name))) + (command + (command_name + (word)) + (address_expression + (reference_name)))) + +================== +Allocation and type operations +================== + +@Object* pointer=@new Object +@pointer=@nullptr +echo @typeof &@pointer +@pointer=@dynamic_cast &@other +@delete @pointer + +--- + +(program + (pointer_declaration + (type_identifier) + (object_name) + (default_value + (assignment_operator) + (new_expression + (type_identifier)))) + (object_assignment + (assignment_reference + (reference_name)) + (assignment_operator) + (nullptr_literal)) + (command + (command_name + (word)) + (typeof_expression + (address_expression + (reference_name)))) + (object_assignment + (assignment_reference + (reference_name)) + (assignment_operator) + (dynamic_cast_expression + (dynamic_cast_target + (type_identifier)) + (address_expression + (reference_name)))) + (delete_statement + (object_reference + (reference_name)))) + +================== +Supershells and strings +================== + +value=@(echo "@this.member") +echo "value=@{this.member} address=&@this.method dereference=*@pointer" +echo @(printf '%s' @object.member) + +--- + +(program + (variable_assignment + (variable_name) + (supershell + (command + (command_name + (word)) + (string + (self_reference + (self)))))) + (command + (command_name + (word)) + (string + (string_content) + (braced_reference + (reference_name)) + (string_content) + (address_expression + (reference_name)) + (string_content) + (pointer_dereference + (reference_name)))) + (command + (command_name + (word)) + (supershell + (command + (command_name + (word)) + (raw_string) + (object_reference + (reference_name)))))) + +================== +References in Bash expressions +================== + +if [[ @this.value == "@object.value" ]]; then + @this.count=$((@this.count + @{object.offset})) +fi + +--- + +(program + (if_statement + (test_command + (binary_expression + (self_reference + (self)) + (string + (object_reference + (reference_name))))) + (object_assignment + (assignment_reference + (reference_name)) + (assignment_operator) + (arithmetic_expansion + (binary_expression + (self_reference + (self)) + (braced_reference + (reference_name))))))) + +================== +References in heredocs +================== + +cat < "$source_paths" + +source_count="$(wc -l < "$source_paths")" +if [[ "$source_count" -eq 0 ]]; then + echo "No Bash++ source files were found." >&2 + exit 1 +fi + +cd "$parser_directory" +"$tree_sitter" parse --quiet --stat --paths "$source_paths" + +for fixture in parser-errors-1.bpp parser-errors-2.bpp; do + fixture_path="$repository_directory/test-suite/tests/sources/$fixture" + if "$tree_sitter" parse --quiet "$fixture_path"; then + echo "Expected $fixture to contain a syntax error." >&2 + exit 1 + fi +done + +printf 'Parsed %d valid Bash++ files and verified 2 recovery fixtures.\n' \ + "$source_count" diff --git a/tree-sitter-bashpp/tree-sitter.json b/tree-sitter-bashpp/tree-sitter.json new file mode 100644 index 0000000..37cf61f --- /dev/null +++ b/tree-sitter-bashpp/tree-sitter.json @@ -0,0 +1,36 @@ +{ + "grammars": [ + { + "name": "bashpp", + "camelcase": "Bashpp", + "scope": "source.bashpp", + "path": ".", + "file-types": [ + "bpp" + ], + "injection-regex": "^(bashpp|bpp)$", + "first-line-regex": "^#!.*\\b(bpp)\\b.*$" + } + ], + "metadata": { + "version": "0.1.0", + "license": "GPL-3.0-or-later", + "description": "Bash++ grammar for Tree-sitter", + "authors": [ + { + "name": "Bash++ contributors" + } + ], + "links": { + "repository": "https://github.com/rail5/bashpp" + } + }, + "bindings": { + "c": false, + "go": false, + "node": false, + "python": false, + "rust": false, + "swift": false + } +}